Android 4 13. Intent
This chapter
- 13.0 Intent
- 13.1 Explicit Intent
- 13.2 Implicit Intent
- 13.3 Launching a Peer Activity
- 13.4 Intent Tabs
Intent is used to launch an appropriate activity. It tells OS an action to take and the data on which that action is to be acted upon.
For example, when we touch a search button, application creates a url that contains the query string. Then, we load the url into a web browser by creating a new intent for viewing the url, then passing that intent to the startActivity method.
Intent is basically a message that is passed between components (such as Activities, Services, Broadcast Receivers, and Content Providers). So, it is almost equivalent to parameters passed to API calls. The fundamental differences between API calls and intents' way of invoking components are:
- API calls are synchronous while intent-based invocations are asynchronous.
- API calls are compile time binding while intent-based calls are run-time binding.
Of course, Intents can be made to work exactly like API calls by using what are called explicit intents, which will be explained later. But more often than not, implicit intents are the way to go and that is what is explained here.
When we make an Android application, we start by subclassing Activity class. Activities provide the reusable/interchangeable parts of the flow of UI across the applications. Then, how does one activity invoke another, and pass information about what the user wants to do? The unit of communication is the Intent class.
An Intent represents an abstraction of a function that one activity requires another activity to perform. Intent form the basis of a system of loose coupling that allows activities to launch another. When an application dispatches an intent, it's possible that several activities might be registered to provide the desired operations.
One component that wants to invoke another has to only express its' intent to do a job. And any other component that exists and has claimed that it can do such a job through intent-filters, is invoked by the android platform to accomplish the job. This means, both the components are not aware of each other's existence and can still work together to give the desired result for the end-user.This invisible connection between components is achieved through the combination of intents, intent-filters and the android platform.
This leads to huge possibilities like:
- Mix and match or rather plug and play of components at runtime.
- Replacing the inbuilt android applications with custom developed applications.
- Component level reuse within and across applications.
- Service orientation to the most granular level, if I may say.
Here is additional description about intent, almost formal.
An intent is an abstract description of an operation to be performed. It can be used with startActivity to launch an Activity, broadcastIntent to send it to any interested BroadcastReceiver components, and startService (Intent) or bindService (Intent, ServiceConnection, int) to communicate with a background Service.
An Intent provides a facility for performing late runtime binding between the code in different applications. Its most significant use is in the launching of activities, where it can be thought of as the glue between activities. It is basically a passive data structure holding an abstract description of an action to be performed. The primary pieces of information in an intent are:
- action
The general action to be performed, such as ACTION_VIEW, ACTION_EDIT, ACTION_MAIN, etc. - data
The data to operate on, such as a person record in the contacts database, expressed as a Uri.
All Android components that wish to be notified via intents should declare intent filters so that Android knows which intents should go to that component. So, we need to add intent-filter elements to our AndroidManifest.xml file. It looks something like this:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android= "http://schemas.android.com/apk/res/android" package="com.bogotobogo.myContacts" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".myContacts" android:label="@string/app_name"> <intent-filter> <action android:name= "android.intent.action.MAIN" /> <category android:name= "android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="7" /> <uses-permission android:name= "android.permission.READ_CONTACTS"/> </manifest>
Note that intent-filter element is under the activity element. In the file, we are declaring that this activity is (1) the main activity for this application and (2) the activity is in the LAUNCHER category, meaning it gets an icon in the Android menu. Because this activity is the main one for the application, Android knows this is the component it should launch when someone chooses the application from the main menu.
Once we have our intent, we need to pass it to Android and get the child activity to launch. Here, we have two options:
- Call startActivity() with the Intent. This will cause Android to find the best matching activity and pass the intent to the activity for handling. The activity will not be informed when the child activity is complete.
- Call startActivityForResult(), passing it the intent and a number which is unique to the calling activity. Android will find the best matching activity and pass the intent over to the activity. The activity will be notified when the child activity is complete via onActivityResult() callback.
In an explicit intent, we actually specify the activity that is required to respond to the intent. In other words, we explicitly designate the target component. This is typically used for application internal messages.
In an implicit intent, the main power of the android design, we just declare an intent and leave it to the platform to find an activity that can respond to the intent. Here, we do not declare the target component and hence is typically used for activating components of other applications seamlessly.
Let's look at our example: borrowed from here
This example has 2 activities:
The InvokingActivity has a button "Invoke Next Activity" which when clicked explicitly calls the InvokedActivity class. The relevant part of the code is here:
Button invokingButton = (Button)findViewById(R.id.invokebutton); invokingButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent explicitIntent = new Intent(InvokingActivity.this,InvokedActivity.class); startActivity(explicitIntent); } });
To exlicitly select an Activity class, we need to create a new Intent. Then, we should pass our intent to startActivity(Intent) method by specifying the current application Context and Activity class.The startActivity(Intent) method is used to start a new activity. It takes a single argument, an Intent which describes the activity to be executed.
After startActivity(Intent) is called, the new Activity (in our case, it is InvokedActivity) will be created and become visible and active, and it will be placed at the top of the activity stack.
The layout for InvokingActivity is defined in /res/main.xml:
and for InvokedActivity in /res/invokedactivity.xml.Here are our java codes: InvokingActivity.java and InvokedActivity.java.
InvokingActivity.java:
package com.bogotobogo.explicitintent; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class InvokingActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button invokingButton = (Button)findViewById(R.id.invokebutton); invokingButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent explicitIntent = new Intent(InvokingActivity.this,InvokedActivity.class); startActivity(explicitIntent); } }); } }
and InvokedActivity.java:
package com.bogotobogo.explicitintent; import android.app.Activity; import android.os.Bundle; public class InvokedActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.invokedactivity); } }
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bogotobogo.ExplicitIntent" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".InvokingActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".InvokedActivity" android:label="@string/app_name" /> </application> </manifest>
Files used in this Explicit Intent example, ExplicitIntent.zip
If we miss the Activity in the AndroidManifest.xml:
<activity android:name=".InvokedActivity" android:label="@string/app_name" />
We get the infamous "Unfortunately, ... has stopped.":
It took me a while figuring out what went wrong. Debugging including the LogCat as shown below did not give any hints:
In the next section, we will see how to work with implicit intents which also needs us to understand intent-filters.
In the previous section, we learned how to use Explicit Intents to invoke activities through a very simple example. Now, we will move on to a more interesting concept of Implicit Intents and Intent Filters.
As described earlier, an implicit intent does not name a target component that should act upon the intent. Android resolves as to which component is best suited to respond to an Implicit Intent. In other words, we can ask the Android to launch an Activity that can perform a given action without knowing which application/Activity will perform the task. How does this happen?
Basically, an Intent object has the following information (among other things like Component name, extras and flags) which is of interest for implicit intents:
- Action
- Category
- Data
So, Android compares the three (action, category and data) to something called Intent Filters that are declared by probable target components who are willing to accept Implicit Intent calls. Intent Filters are the way of any component to advertise its own capabilities to the Android system. This is done declaratively in the AndroidManifest.xml file.
So here are some important points to remember:
- Implicit Intents do not specify a target component.
- Components willing to receive implicit intents have to declare their ability to handle a specific intent by declaring intent filters.
- A component can declare any number of Intent Filters.
- There can be more than one component that declares the same Intent Filters and hence can respond to the same implicit intent. In that case, the user is presented both the component options and he can choose which one he wants to continue with.
- We can set priorities for the intent filters to ensure the order of responses.
There are 3 tests conducted in order to match an intent with intent filters:
- Action Test
- Category Test
- Data Test
Finally, we'll look at declaring an implicit intent in one activity which will invoke one of the native activities of the platform by matching the intent filters declared by the same.
The ImplicitIntent Activity creates an implicit intent object contacts. This intent object's component is not set. However, the action is set to android.content.intent.ACTION_VIEW and the data's URI is set to People.CONTENT_URI.
Such an intent matches with the intent filter declared by the view contacts native activity.
So, when we run this application, it displays the native UI for viewing the existing contacts on the phone!
Here is the relevant piece of code for the same:
Button viewContacts = (Button)findViewById(R.id.ViewContacts); viewContacts.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent contacts = new Intent(); contacts.setAction(android.content.Intent.ACTION_VIEW); contacts.setData(People.CONTENT_URI); startActivity(contacts); } });
In this manner many of the native applications can be seamlessly invoked as one of the activities in our applications through implicit intents.
Here are our Java code, ImplicitIntent.java:
package com.bogotobogo.ImplicitIntent; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Bundle; import android.provider.ContactsContract; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class ImplicitIntentActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ViewContacts(); } private void ViewContacts() { try { Button viewContacts = (Button)findViewById(R.id.ViewContacts); viewContacts.setOnClickListener(new OnClickListener() { public void onClick(View v) { Intent contacts = new Intent(); contacts.setAction(android.content.Intent.ACTION_VIEW); contacts.setData(ContactsContract.Contacts.CONTENT_URI); startActivity(contacts); } }); } catch (ActivityNotFoundException anfe) { Log.e("ViewContacts","Viewing of Contacts failed", anfe); } } }
The main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/LinearLayout01" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <Button android:text="@string/InvokeImplicit" android:id="@+id/ViewContacts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" /> </LinearLayout>
strings.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Implicit Intent</string> <string name="InvokeImplicit">View Contacts</string> </resources>
After we hit the button:
Files used in this Implicit Intent example, ImplicitIntent.zip
In this example, we'll have two fields for the latitude and longitude, and a button asking a map for the location.
Here is the layout, main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TableLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:stretchColumns="1,2"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="2dip" android:paddingRight="4dip" android:text="@string/location"/> <EditText android:id="@+id/lat" android:inputType="number" android:layout_width="fill_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:editable="true" android:singleLine="true" android:layout_weight="1"/> <EditText android:id="@+id/lon" android:inputType="number" android:layout_width="fill_parent" android:layout_height="wrap_content" android:cursorVisible="true" android:editable="true" android:singleLine="true" android:layout_weight="1"/> </TableLayout> <Button android:id="@+id/map" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/show"/> </LinearLayout>
strings.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, LaunchActivity!</string> <string name="app_name">Launch</string> <string name="location">Location</string> <string name="show">Show me the map!</string> </resources>
Our Java code LaunchActivity.java:
package com.bogotobogo.Launch; import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class LaunchActivity extends Activity { private EditText lat; private EditText lon; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); Button btn=(Button)findViewById(R.id.map); lat=(EditText)findViewById(R.id.lat); lon=(EditText)findViewById(R.id.lon); btn.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { String _lat=lat.getText().toString(); String _lon=lon.getText().toString(); Uri uri=Uri.parse("geo:"+_lat+","+_lon); startActivity(new Intent(Intent.ACTION_VIEW, uri)); } }); } }
The button's OnClickListener takes the latitude and longitude, put them into geo scheme Uri.
Uri uri=Uri.parse("geo:"+_lat+","+_lon);
Then, starts the activity after creating an intent requesting to view this Uri (ACTION_VIEW)
startActivity(new Intent(Intent.ACTION_VIEW, uri));
In this section, we'll have a tab browser using an Intent. Each tab will launch its own browser Activity. Actually Android's tab-management framework put the Activity's UI into each tab.
TabActivity class is deprecated.
New applications should use Fragments instead of this class; to continue to run on older devices, you can use the v4 support library which provides a version of the Fragment API that is compatible down to DONUT (Android 1.6/API Level 4)
Here is the source for our main activity which is hosting the TabView, IntentTab.java:
ppackage com.bogotobogo.IntentTab; import android.app.TabActivity; import android.content.Intent; import android.os.Bundle; import android.widget.TabHost; public class IntentTab extends TabActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); TabHost host=getTabHost(); host.addTab(host.newTabSpec("one").setIndicator("BoGoToBoGo"). setContent(new Intent(this, BoGoBrowser.class))); host.addTab(host.newTabSpec("two").setIndicator("Android"). setContent(new Intent(this, AndroidBrowser.class))); } }
Here, we are using TabActivity as the base class, and so we don't have to use our own layout for the view since TabActivity supplies it for us. So, we just get access to the TabHost and add two tabs. Each tab specifies an Intent that directly refers to another class: BoGoBrowser and AndroidBrowser, respectively.
Other sources we need are:
BoGoBrowser.java:
package com.bogotobogo.IntentTab; import android.app.Activity; import android.os.Bundle; import android.webkit.WebView; public class BoGoBrowser extends Activity { WebView browser; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); browser=new WebView(this); setContentView(browser); browser.loadUrl("http://bogotobogo.com"); } }
AdroidBrowser.java:
package com.bogotobogo.IntentTab; import android.app.Activity; import android.os.Bundle; import android.webkit.WebView; public class AndroidBrowser extends Activity { WebView browser; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); browser=new WebView(this); setContentView(browser); browser.loadUrl("http://www.android.com/"); } }
One more thing, we need to add the following lines to AndroidManifest.xml.
<activity android:name=".BoGoBrowser" /> <activity android:name=".AndroidBrowser" /> <uses-permission android:name="android.permission.INTERNET" />
So, the manifest file should look like this:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bogotobogo.IntentTab" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" /> <uses-permission android:name="android.permission.INTERNET" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".IntentTab" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".BoGoBrowser" /> <activity android:name=".AndroidBrowser" /> </application> </manifest>
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization