In one of the previous tutorials, “Storing App-Related Data in Your Android Apps,” we introduced various approaches regarding how to store important data pertaining to your own app. This tutorial is about interacting with other apps by sending data to or receiving data from them. One Android app usually contains multiple activities. For the current Android system, every activity is equivalent to one device display you can see at a time. To switch from one activity to another, an Android intent must be defined and instantiated to identify the right activity to move on to. How does an activity know whether it can be activated? It specifies an expression, namely an intent filter so that it allows certain types of intents only. We will briefly cover these concepts by using simple examples.
Intents and Intent Filters
The current Android system allows one running activity with a user interface on the device display at a single time. You will need to utilize a messaging mechanism that enables you to jump from one activity to another inside the same app or across different apps. Intents and intent filters are the key objects in the design. In fact, intents can also start an activity or service as well as deliver a broadcast.
How do you construct an intent? There are some main components inside the intent object: name, action to perform, data to be used by the intent action, category about what kind of component that should handle the intent, flags as to how to launch an activity, and extras you can create a bundle object into.
There are two types of intents: implicit and explicit. Explicit intents are basically those with fully and uniquely qualified class names. Implicit types usually declare a general action (such as viewing a photo) to perform and thus more than one activity may be qualified and chosen to carry out the action.
Intent filters are basically listed components in an app’s manifest file and used to decide whether activity requests should be allowed. Seeing intents and intent filters in action in the following examples is the best way to understand them./p>
Sending Data to Another App
We start from the simplest example by sending data to another app to start a new activity. An intent is created with only the action specified as Intent.ACTION_WEB_SEARCH without the name. That is, an implicit type of intent. As mentioned previously, implicit intents can have multiple qualified activities that can perform the requested action. The system then can choose which default activity to continue processing the intent. However, there could even be no qualified activity at all, so a sanity check to ensure there exists at least one activity is a good practice, especially in the case of implicit intents. packageManager.queryIntentActivities is called for this purpose.
public class SendToAnother extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startTest(); } private void startTest() { // create an intent before calling another app Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.putExtra(SearchManager.QUERY, "House of Cards"); // check if another app is available PackageManager packageManager = getPackageManager(); List<ResolveInfo> apps = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); boolean intentExists = apps.size() > 0; // call another app if it exists // otherwise, alert the user if (intentExists) startActivity(intent); else (new AlertDialog.Builder(SendToAnother.this) .setMessage("Intent '" + intent.toString() + "' does not exist") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create()).show(); } }
Receiving Output Data from Another App
The preceding example takes us from one activity to another but does not expect any result back from the called activity. The following illustrates the case when one activity, ActivityA, calls another activity, ActivityB, and expects to receive the result. ActivityA basically creates an intent with lower and upper bounds as integers stored into a Bundle object. This is an explicit type of intent because it clearly specifies the intent class name to call. startActivityForResult() is used to indicate that a result is expected from the called activity, ActivityB.
public class ActivityA extends Activity { protected static final int ID_ACT_SUM = 6001; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.acta); startTest(); } private void startTest() { // create a bundle to hold the data Bundle bundle = new Bundle(); bundle.putInt("lower", 1); bundle.putInt("upper", 100); // call another activity Intent intent = new Intent(ActivityA.this, ActivityB.class); intent.putExtras(bundle); startActivityForResult(intent, ID_ACT_SUM); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); // check the result pertaining to this request code if (requestCode == ID_ACT_SUM && resultCode == RESULT_OK && data != null) { Bundle bundle = data.getExtras(); if (bundle != null) { int s = bundle.getInt("sum"); (new AlertDialog.Builder(ActivityA.this) .setMessage("ActivityB returns the sum: " + s) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create()).show(); } } } }
In ActivityB, it uses getIntent() to check whether there is any incoming action request. If the Bundle object contains lower and upper bounds, we calculate the sum of all the integers in between them. Once the action is completed, the result is stored into an intent bundle and shipped back to the calling activity by setResult(). It closes the activity with finish().
public class ActivityB extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startTest(); } private void startTest() { Intent intent = getIntent(); String cname = intent.getComponent().getClassName(); String action = intent.getAction(); String type = intent.getType(); Bundle bundle = intent.getExtras(); if (bundle != null) { // read the request parameters int n1 = bundle.getInt("lower"); int n2 = bundle.getInt("upper"); // do the calculation int low = (n1 > n2) ? n2 : n1; int high = (n1 > n2) ? n1 : n2; int total = 0; for (int j = low; j <= high; j++) { total += j; } // send back the result Bundle b = new Bundle(); b.putInt("sum", total); Intent i = new Intent(); i.putExtras(b); setResult(RESULT_OK, i); finish(); } } }
For receiving, ActivityA implements onActivityResult() to check the request result and decode the bundle content. ID_ACT_SUM is just an arbitrarily assigned integer to identify this specific request because the current activity can have many intent action requests.
Making Your App Available for Other Apps
An Android system builds and maintains a comprehensive list of intents supported by the installed apps. The information is updated according to their intent filters whenever there are changes made for the apps. Therefore, <intent-filter> elements in your manifest file are used to control the usage for the corresponding <activity>. There are three key components to set up the intent filter: action, category, and data. In the following example, assume we are going to allow our app to handle any received image when intent action android.intent.action.SEND orandroid.intent.action.VIEW is requested. This is what it should look like on the app’s manifest file.
<intent-filter> <action android_name= "android.intent.action.SEND" /> <action android_name= "android.intent.action.VIEW" /> <category android_name= "android.intent.category.DEFAULT" /> <data android_mimeType= "image/*" /> </intent-filter>
As shown in Figure 1, whenever you click to share an image, the system will display our app in the lists.
Figure 1: App through Intent Filter
Also, in the app, we need to handle the incoming intent by checking its action and type. The following code just displays the received image on screen.
private void handleReceivedIntent() { Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION_SEND.equals(action) && type != null) { if (type.startsWith("image/")) { Uri receivedUri = Uri)intent.getParcelableExtra(Intent.EXTRA_STREAM); ImageButton ib = (ImageButton)findViewById(R.id.button_image); ib.setImageURI(receivedUri); } } }
Sharing Data Through Intent Chooser
Data can be shared through the system’s intent chooser as well. Through Intent.createChooser(), we can create an intent that the user will select a qualified app to perform. The following example basically tries to request an intent action Intent.ACTION_SEND with an image. Intent chooser will pop up the app list, as in Figure 2, when the following code is executed.
public class ShareThroughChooser extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); startTest(); } private void startTest() { // specify our test image location Uri url = Uri.parse("android.resource://" + getPackageName() + "/" + R.drawable.test); // set up an intent to share the image Intent share_intent = new Intent(); share_intent.setAction(Intent.ACTION_SEND); share_intent.setType("image/png"); share_intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(new File(url.toString()))); share_intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); share_intent.putExtra(Intent.EXTRA_SUBJECT, "share an image"); share_intent.putExtra(Intent.EXTRA_TEXT, "This is an image to share with you"); // start the intent try { startActivity(Intent.createChooser(share_intent, "ShareThroughChooser Test")); } catch (android.content.ActivityNotFoundException ex) { (new AlertDialog.Builder(ShareThroughChooser.this) .setMessage("Share failed") .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).create()).show(); } } }
Figure 2: Share through Intent Chooser
Conclusion
This short tutorial covers some basic ideas about the interaction between your app and others by sharing formatted data. This is more like the message sending and receiving mechanism generally seen in other computer languages also.
There are more advanced ways to share data by configuring your app with secure file handles through file providers and sharable directories. Sharing data across devices—for example, NFC—is increasingly popular as well. However, those topics are beyond the scope of this brief introduction of simple data sharing.
References
- Download and save the entire software project: See the link at the article’s end
- Android Developers at: http://www.android.com/developers/
- Androidlet at http://www.androidlet.com
About the Author
Chunyen Liu has been a software professional in Taiwan and the United States. He is a published author of 30+ articles and 100+ tiny apps, software patentee, technical reviewer, and programming contest winner by ACM/IBM/SUN. He holds advanced Computer Science degrees and trained in 20+ graduate-level courses. On the non-technical side, he is a certified coach, certified umpire, and rated player of USA Table Tennis, having won categorized events at State Championships and US Open.