Archive for the ‘ Android ’ Category

Android Development: Send Tweet Action

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.

Tech News – November 12th

Apple vs. HTC – Will HTC Phones be Banned?
I’m not sure the denying access to Android devices strategy will actually help Apple. #backlash (from both expected and unexpected sources)

Apple suggests design options for Samsung (to avoid lawsuits)
Helpful ideas like: don’t make your devices rectangular, don’t make the front bezel black and give it a cluttered appearance. #unbelievable

Tech firm wants to ban office e-mail
Interesting idea – communicate via IM and wiki like pages to avoid spam. #wikimail
Three issues I see with that approach:

  1. I see the change introducing some level of inefficiency to something that is already efficient and intuitive.
  2. How do you receive communication from outsite the company, like from other companies that use…email?
  3. If there is any portal from the outside to the new approaches, how long until spam becomes a factor there also?

Dropbox for Teams
This is a great idea, but security would be my main concern. Intranet with vpn access still seems like a better solution for mission critical or highly confidential information. #casualDocumentsOnly

My First Android App on the Marketplace!

This Android App provides a fully functional blog interface for Michelle Malkin’s blog. It supports several ways of listing posts, a way to Star posts you like, post viewing, comment viewing, comment posting as well as new post notifications. A number of configuration options allow you to tweak text and image sizes as well as set the notification check frequency or completely disable them.

The primary benefit from the app is a much cleaner and very intuitive user interface (have you tried to read blogs for any length of time on any mobile browser?) that is themed for the site. It is also much more efficient since the app is not requesting fully rendered pages but instead requesting data via xmlrpc – so the response on the app is much faster than rendering and pulling an entire page. It also has to potential to reduce server load (theoretically – if enough people switch to using the app instead of the browser).

To give it a try, search for ‘Michelle Malkin’ in the android Marketplace. Alternatively, you can just scan the barcode shown above with your phone.

By the way, if you are running WordPress and are interested in an app like this for your site, feel free to contact me for a quote and timeframe.