I recently had a need within an Android application to provide a tweet button. It is really easy to launch a Share action in Android:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, title + "\n" + content);
this.startActivity(Intent.createChooser(intent, "Share..."));
This brings up a selection dialogue which presents the users with every app they have installed on their device which handles a “text/plain” type request. For most scenarios, this is the best way to go, although sometimes the list of applications can be a bit overwhelming.
However, I needed a “Tweet This” button related to the content that was being displayed. Searching the net yielded a significant number of people asking exactly how to do this. Some responses just chided the question, and advised strongly that the standard intent and letting the user choose from what they have installed is the right way to do it. This isn’t bad advice, but I already have a social share button in this project that does exactly that. The project requirements also include a dedicated “Tweet” button and it makes no sense for a button labeled “Tweet” to pull up a list that buries my Twitter client (or clients!) among Gmail, Facebook, Google+, Dropbox, Evernote, LinkedIn, GoogleVoice, and others.
This post details how I managed to pull it off. I’m very aware that the solution is far from perfect and there is much room for improvement. It doesn’t support every known Twitter client out there but it can be extended as needed. The bottom line is it works, and the “Tweet” button actually tries to send a tweet. After spending the better part of a day putting this together, I thought I would share it here and hope it helps someone else out also.
Step 1 involves building a list of supported Twitter clients. This list can be easily expanded once you know the intent activity of any client. I did this by installing the 7 clients in the list, looking at the list of intents and then adding them to the list and testing each one. I hate hardcoding anything and I will probably move this list to arrays.xml down the line, but there really is not a better way to do this. All of these save one (UberSocial) support the generic “text/plain” intent type, which yields no way to identify a twitter client other than knowing the activity name in advance. (By the way, UberSocial supports the “application/twitter” intent type, which is a cool idea, however they are (as far as I know) the only client that supports this intent type which makes it pretty worthless at this point.)
Here is the code, which stores our supported Twitter Client list in a Map<String,String> named knownTwitterClients:
// Build list of Known Twitter Clients
private void buildKnownTwitterClientsList() {
knownTwitterClients = new HashMap<String, String>();
knownTwitterClients.put("Twitter", "com.twitter.android.PostActivity");
knownTwitterClients.put("UberSocial", "com.twidroid.activity.SendTweet");
knownTwitterClients.put("TweetDeck", "com.tweetdeck.compose.ComposeActivity");
knownTwitterClients.put("Seesmic", "com.seesmic.ui.Composer");
knownTwitterClients.put("TweetCaster", "com.handmark.tweetcaster.ShareSelectorActivity");
knownTwitterClients.put("Plume", "com.levelup.touiteur.appwidgets.TouiteurWidgetNewTweet");
knownTwitterClients.put("Twicca", "jp.r246.twicca.statuses.Send");
}
Step 2 is comparing our list of known clients with apps actually installed on the device. This is done by reading through the Intent Activities on the device and storing any that match our list of known Twitter Client. This list is stored in a Map<String, ActivityInfo> named foundTwitterClients.
// Detect Twitter Clients
public boolean detectTwitterClients() {
buildKnownTwitterClientsList();
foundTwitterClients = new HashMap<String, ActivityInfo>();
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
PackageManager pm = getPackageManager();
List<ResolveInfo> activityList = pm.queryIntentActivities(intent, 0);
int len = activityList.size();
for (int i = 0; i < len; i++) {
ResolveInfo app = (ResolveInfo) activityList.get(i);
ActivityInfo activity = app.activityInfo;
if (knownTwitterClients.containsValue(activity.name)) {
foundTwitterClients.put(activity.name, activity);
}
}
return false;
}
Step 3 is a method that resolves the ComponentName object that will be used to launch a tweet request. Notice that I’ve added an extra string named preferredTwitterClient. This code grabs the first found Twitter client from our list and sets that as the client that will be used. Then it checks to see if a preferredTwitterClient is specified, and if that app is installed on the device, then that client is used instead. The thought behind this feature is to eventually have a settings option that lists the available Twitter Clients on the device and allow the users to pick the one they prefer.
// Resolve the twitter client component name
public ComponentName getTwitterClientComponentName() {
ComponentName result = null;
if (foundTwitterClients.size() > 0) {
ActivityInfo tweetActivity = null;
for(Map.Entry<String, ActivityInfo> entry : foundTwitterClients.entrySet()) {
tweetActivity = entry.getValue();
break;
}
if (preferredTwitterClient != null) {
String activityName = knownTwitterClients.get(preferredTwitterClient);
if(foundTwitterClients.containsKey(activityName)) {
tweetActivity = foundTwitterClients.get(activityName);
}
}
result = new ComponentName(tweetActivity.applicationInfo.packageName, tweetActivity.name);
}
return result;
}
Step 4 is simply coding the “Tweet” button handler method. Notice that if none of our known Twitter clients are found on the device, this method simply fires off the standard “text/plain” application chooser. The variables title and permalink are globals in the activity; you can set the Intent.EXTRA_TEXT to whatever suits your application.
// Tweet button handler
public void onTweetClick(View v) {
ComponentName targetComponent = getTwitterClientComponentName();
if(targetComponent != null) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setComponent(targetComponent);
String intentType = (targetComponent.getClassName().contains("com.twidroid")) ?
"application/twitter" : "text/plain";
intent.setType(intentType);
intent.putExtra(Intent.EXTRA_TEXT, title + "\n" + permalink);
this.startActivity(intent);
} else {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, title + "\n" + permalink);
this.startActivity(Intent.createChooser(intent, "Share..."));
}
}
I hope that helps. The solution would be much cleaner if there was an intent type that identified Tweet actions. Ideally, every client should support both “text/plain” and “application/twitter” intent types – which would eliminate the need to manually build a list of known twitter clients – a significant improvement to what I’ve done here.
Future: I’m planning on adding a preference selector where the user could select the installed client they prefer, but another really nice idea would be to instead display a selector list just like the createChooser does but that only lists Twitter Clients.
If your favorite Twitter Client isn’t in the list and you want to do the research and find their intent activity, post it in the comments and I will add it to my knownTwitterClients map.




