Monday, July 11, 2011

Understanding Intents and Intent-Filters in Android

Intents or Intent messaging is the Android’s way to pass messages/information to various components of Android. An Intent object contains information that the target component can act upon. Through Intents, your apps can trigger Activities, Services and Broadcast Receivers. The most common use of intents in any application is to directly launch an Activity or Service to perform action on the data being passed with the Intent object.

A sample use of Intent is shown in the following example.
1. To launch an Activity
Intent intent = new Intent(context, TagetActivity.class);
startActivity(intent);
2. To launch a Service
Intent intent = new Intent(context, TargetService.class);
startService(intent);
To pass some data within your intents you need pass them within the Intent object like this.
intent.put(key, value);
This way you can pass basic data-types that are supported by the Intent class. In these examples, we are specifying the component names explicitly, i.e, TagetActivity activity and TargetService service. These intents are examples of Explicit Intents wherein you specify the target component that you want to handle your data.

There would be situations where you would want the user to choose the target component, or to put in another way, the target component is determined dynamically at run-time depending on various factors. For an example, you would like to write a E-Mail app. The basic requirement would be that whenever a user clicks on an e-mail address, your app should be triggered, and it should be designed to capture the e-mail address that was clicked. This is where, Implicit Intents come into picture. Since, the e-mail address link that the user clicks on might not necessarily be a part of your application, you do not have the privilege of launching your Compose Activity on the click event of the link. What would you do in such a case?

What happens when the e-mail link is clicked?
When an email link is clicked, the app would create an Implicit Intent setting it up with the proper data and broadcast it. Something like this:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.put(Intent.EXTRA_EMAIL, {email addresses}];
startActivity(intent);
In the above code snippet, we can see that the Intent here doesn’t specify the component name. However, it specifies an action and the list of email addresses. For this example, we just need to pass a string array with just one email address that the user has clicked on.

How would Android know which activity should be launched (or service for example). In such cases, Android tries to find the best match, i.e, the component that can handle such a payload. In case it finds multiple components, it gives the user a choice to make. For instance, if you have multiple email clients installed and all of them can handle such type of Intents, the user gets a list of all these apps (see image). He can then select one which would launch that particular component (Activity for this example). If it fails to find any components to handle this Intent, an exception will be thrown (ActivityNotFoundException in this case). Here we have two email applications that can handle this intent. The user is given a choice to select one of them when an e-mail link is clicked. Depending on the user’s choice, the corresponding activity will be launched.

How do we write an Activity that can handle such Intents?

We have to specify or rather design one of our activity, the compose screen of our email client that can handle such intents. For that, we need to specify Intent-Filters for this specific activity in the AndroidManifest.xml file.
<activity android:name=".ComposeActivity"
    android:label="@string/app_name">
    <intent-filter android:label="@string/app_name">
         <action android:name="android.intent.action.SEND" />
         <data android:scheme="mailto" />
         <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
The above code itself is self-explanatory. Your activity now can handle intents that are launched with this signature. The action is “android.intent.action.ACTION_SEND” and the data scheme is “mailto”, i.e, any email address. So, in your ComposeActivity, you can retrieve the email address list by querying the intent for the key, EXTRA_EMAIL and you will get the list of email addresses (one in this case). Typically, you will need to populate the Send To address field of your ComposeActivity now.

The intent resolution process tries to find a best match for the intent. Once it finds an Intent and launches the Activity with or without user interruption(in case multiple matches are found), the responsibility of handling the intent now lies with the target component. To finish off the article, here is the way to extract the email address that had been clicked.
Bundle data = getIntent().getExtras();
String[] emailAddresses = data.get(Intent.EXTRA_EMAIL);
The developer docs on Intent, Intent Filters and Intent Resolution mechanism is a nice read to understand the Intent mechanism of Android. You can find other examples of the types of data and schemes that you can design your apps for.