Sharing content on Android made simple

Introduction

I bet you had a moment when you needed to share something from your app. And if you didn't, trust me, you'll get there eventually.

There is bunch of rules that one should keep in mind when sharing content. Some of them are described in Android training, other in Intent class docs.

The truth is, some apps out there follow the contract introduced by Google, but others break it in cold blood.

Regardless of the reason, there are cases, where you'd like to filter out the apps, that will be displayed in share dialog. Or you'd like to write custom dialog. Anyway, you'd like to have some control over the sharing dialog behaviour.

If that's the case, you're in the right place! :)

Show me the code!

Yep, you're here for the code (as we all know, the Internet is for code, right? ;))

The most basic sharing intent code would look something like this:

Intent sendIntent = new Intent(Intent.ACTION_SEND);  
sendIntent.putExtra(Intent.EXTRA_SUBJECT, shareContentSubject);  
sendIntent.putExtra(Intent.EXTRA_TEXT, shareContent);  
sendIntent.setType("text/plain");  
startActivity(Intent.createChooser(sendIntent, "Share with"));  

Above code will show standard share dialog allowing user to pick the appropriate app.

What if you need to filter out the apps or share to a distinct application (that e.g. don't have the SDK like Facebook or Twitter)?

Let's say we'd like to check if a given application is installed. There are cases, when you need this kind of information.

Having in mind, that Android has a very strict rule, that package name can't be changed for the application, makes it possible to map the application in question to the package.

To find the package name for the app, it's enough to search for it in Google Play on your browser.

Package name of Twitter application in Google Play

Here we see, that package name for twitter is com.twitter.android.

Let's see the code, that will go through all installed apps on the device and check if given packages are avaliable. For further usage, we'll write it in a way, that it returns list of matching ResolveInfo objects.

static Collection<ResolveInfo> findMatchingResolveInfo(PackageManager pm, Intent messageIntent, String... lookupPackages) {  
    Collection<ResolveInfo> resInfo = findAllMatching(pm, messageIntent);
    Collection<ResolveInfo> matchingResInfo = new HashSet<>(resInfo.size());
    for (ResolveInfo resolveInfo : resInfo) {
        String packageName = resolveInfo.activityInfo.packageName;
        for (String lookupPackage : lookupPackages) {
            if (packageName.contains(lookupPackage)) {
                matchingResInfo.add(resolveInfo);
                break;
            }
        }
    }
    return matchingResInfo;
}

Having this, we can introduce a method to check if the listed packages are installed:

static boolean isPackageInstalled(PackageManager pm, Intent messageIntent, String... lookupPackages) {  
    return !findMatchingResolveInfo(pm, messageIntent, lookupPackages).isEmpty();
}

Methods are prepared in a way to pass several packages, as it's sometimes easier to use. So, we checked that our app is installed; how can we make it, so our Intent will be sent to a specific app?

There are two ways to do so. If we have only one target app, we should use Intent.setPackage. On the other hand, if you'd like to show a picker prioritizing given apps, you can use Intent.EXTRA_INITIAL_INTENTS.

Having in mind the previous method implementented, we can do it like that:

static Intent filterSendAction(PackageManager pm, Intent messageIntent, String... lookupPackages) {  
    Collection<ResolveInfo> matchingResInfo = findMatchingResolveInfo(pm, messageIntent, lookupPackages);

    if (!matchingResInfo.isEmpty()) {
        if (matchingResInfo.size() == 1) {
            messageIntent.setPackage(matchingResInfo.iterator().next().activityInfo.packageName);
        } else {
            List<LabeledIntent> intentList = new ArrayList<>();
            for (ResolveInfo resolveInfo : matchingResInfo) {
                Intent intent = new Intent(messageIntent);
                ActivityInfo activityInfo = resolveInfo.activityInfo;
                intent.setComponent(new ComponentName(activityInfo.packageName, activityInfo.name));
                intentList.add(new LabeledIntent(intent, activityInfo.packageName, resolveInfo.loadLabel(pm), resolveInfo.icon));
            }
            LabeledIntent[] extraIntents = intentList.toArray(new LabeledIntent[intentList.size()]);
            messageIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents);
        }
    }
    return messageIntent;
}

What's next?

Those are the basics of how you can customize your sharing functionality.
Using the code from findMatchingResolveInfo with Intent.setPackage method you can write a completely custom share view.

For more info please check our github sample project, containing really simple custom share view. That's it for now.
Feel free to contact me if you find the post useful or you have some feedback regarding the content (especially if you find a bug in my sample project ;))