- Android Application Overview
- Activity Lifecycle
- Multiple Activities
Multiple Activities
Even the simplest applications have more than one functionality. Hence, there is often a need to deal with multiple activities. For example, a game can have two activities: a high scores screen and a game screen. A notepad can have three activities: view a list of notes, read a selected note, and edit a selected or new note.
The main activity, as defined in the AndroidManifest XML file, is started when the application is started. This activity can launch another activity, usually after a trigger event. This causes the main activity to pause while the secondary activity is active. When the secondary activity ends, the main activity is brought to the foreground and resumed.
To activate a particular component of the application, an intent naming the component explicitly is used. If instead the requirements of an application can be specified by intent filters, an implicit intent can be used. The system then determines the best component or components to use, even if it is in a separate application or native to the OS. Note that unlike other activities, implicit intents that reside in other applications do not need to be declared in the current application's AndroidManifest file.
Android utilizes implicit intents as often as possible, providing a powerful framework for modular functionality. When a new component is developed that meets the required implicit intent filter, it can be used in place of an Android internal intent. For example, say a new application for displaying phone contacts is loaded on an Android device. When a user selects a contact, the Android system finds all available activities with the proper intent filter for viewing contacts and asks the user to decide which one should be used.
Recipe: Using Buttons and TextView
To fully demonstrate multiple activities, it is useful to use a trigger event. A button press is introduced here for that purpose. The steps to adding a button to a given layout and assigning an action to a button press are
- Put a button in the designated layout XML file:
<Button android:id="@+id/trigger" android:layout_width="100dip" android:layout_height="100dip" android:text="Press this button" />
- Declare a button that points to the button ID in the layout file:
Button startButton = (Button) findViewById(R.id.trigger);
- Specify a listener for when the button is clicked:
//setup button listener startButton.setOnClickListener(new View.OnClickListener() { //insert onClick here });
- Override the onClick function for the listener to do the required action:
public void onClick(View view) { // do something here }
To show the result of an action, it is useful to change the text on the screen. The steps for defining a text field and changing it programmatically are
- Put a text field in the designated layout XML file with an ID. It can also be initialized to some value (here, it can be initialized to the string named "hello" in the strings.xml file):
<TextView android:id="@+id/hello_text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" />
- Declare a TextView that points to the TextView ID in the layout file:
private TextView tv = (TextView) findViewById(R.id.hello_text);
- If the text needs to be changed, use the setText function:
tv.setText("new text string");
These two UI techniques are used in the subsequent recipes in this chapter. A more complete demonstration of UI techniques is covered in Chapter 4.
Recipe: Launching Another Activity from an Event
In this recipe, MenuScreen is the main activity as shown in Listing 2.8. It launches the PlayGame activity. Here the trigger event is implemented as a button click using the Button widget.
When a user clicks the button, the startGame() function runs; it launches the PlayGame activity. When a user clicks the button in the PlayGame activity, it calls finish() to return control to the calling activity. The steps for launching an activity are
- Declare an Intent that points to the activity to be launched.
- Call startActivity on this intent.
- Declare the additional activity in the AndroidManifest.
Listing 2.8. src/com/cookbook/launch_activity/MenuScreen.java
package com.cookbook.launch_activity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MenuScreen extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); //setup button listener Button startButton = (Button) findViewById(R.id.play_game); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startGame(); } }); } private void startGame() { Intent launchGame = new Intent(this, PlayGame.class); startActivity(launchGame); } }
The PlayGame activity shown in Listing 2.9 is simply a button with a onClick listener that calls finish() to return control to the main activity. More functionality can be added as needed to this activity, and multiple branches of the code can each lead to their own finish() calls.
Listing 2.9. src/com/cookbook/launch_activity/PlayGame.java
package com.cookbook.launch_activity; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; public class PlayGame extends Activity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.game); //setup button listener Button startButton = (Button) findViewById(R.id.end_game); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { finish(); } }); } }
The button must be added to the main layout as shown in Listing 2.10, with the ID play_game to match what was declared in Listing 2.8. Here, the size of the button is also declared in device-independent pixels (dip), as discussed more in Chapter 4.
Listing 2.10. res/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" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <Button android:id="@+id/play_game" android:layout_width="100dip" android:layout_height="100dip" android:text="@string/play_game" /> </LinearLayout>
The PlayGame activity references its own button ID end_game in the R.layout.game layout resource that corresponds to the layout XML file game.xml, as shown in Listing 2.11.
Listing 2.11. res/layout/game.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" > <Button android:id="@+id/end_game" android:layout_width="100dip" android:layout_height="100dip" android:text="@string/end_game" android:layout_centerInParent="true" /> </LinearLayout>
Although the text can be written explicitly in each case, it is good coding practice to define variables for each string. In this recipe, the two string values play_game and end_game need to be declared in the string XML resource file, as shown in Listing 2.12.
Listing 2.12. res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">This is the Main Menu</string> <string name="app_name">LaunchActivity</string> <string name="play_game">Play game?</string> <string name="end_game">Done?</string> </resources>
Finally, the AndroidManifest XML file needs to register a default action to the new class PlayGame, as shown in Listing 2.13.
Listing 2.13. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.cookbook.launch_activity"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MenuScreen" 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=".PlayGame" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="3" /> </manifest>
Recipe: Launching an Activity for a Result Using Speech to Text
In this recipe, launching an activity for a result is demonstrated. It also demonstrates how to utilize speech to text functionality from Google's RecognizerIntent and print the result to the screen. Here, the trigger event is a button press. It launches the RecognizerIntent activity, which does speech recognition on sound from the microphone and converts it into text. When finished, the text is passed back to the calling activity.
Upon return, the onActivityResult() function is first called with the returned data, and then the onResume() function is called to continue the activity as normal. The calling activity can have a problem and not return properly. Therefore, the resultCode should always be checked to ensure RESULT_OK before continuing to parse the returned data.
Note that in general any launched activity that returns data causes the same onActivityResult() function to be called. Therefore, a request code is customarily used to distinguish which activity is returning. When the launched activity finishes, it returns control to the calling activity and calls onActivityResult() with the same request code.
The steps for launching an activity for result are
- Call startActivityForResult() with an intent, defining the launched activity and an identifying requestCode.
- Override the onActivityResult() function to check on the status of the result, check for the expected requestCode, and parse the returned data.
The steps for using RecognizerIntent are
- Declare an intent with action ACTION_RECOGNIZE_SPEECH.
- Add any extras to the intent; at least EXTRA_LANGUAGE_MODEL is required. This can be set as either LANGUAGE_MODEL_FREE_FORM or LANGUAGE_MODEL_WEB_SEARCH.
- The returned data bundle contains a list of strings with possible matches to the original text. Use data.getStringArrayListExtra to retrieve this data. This should be cast as an ArrayList for use later.
A TextView is used to display the returned text to the screen. The main activity is shown in Listing 2.14.
The additional supporting files needed are the main.xml and strings.xml, which need to define a button and the TextView to hold the result. This is accomplished using Listing 2.10 and 2.12 in the "Launching Another Activity from an Event" recipe. The AndroidManifest needs to declare only the main activity, which is the same as the basic "Creating an Activity" recipe. The RecognizerIntent activity is native to the Android system and does not need to be declared explicitly to be utilized.
Listing 2.14. src/com/cookbook/launch_for_result/RecognizerIntent Example.java
package com.cookbook.launch_for_result; import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.speech.RecognizerIntent; import android.view.View; import android.widget.Button; import android.widget.TextView; public class RecognizerIntentExample extends Activity { private static final int RECOGNIZER_EXAMPLE = 1001; private TextView tv; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView) findViewById(R.id.text_result); //setup button listener Button startButton = (Button) findViewById(R.id.trigger); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { // RecognizerIntent prompts for speech and returns text Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Say a word or phrase\nand it will show as text"); startActivityForResult(intent, RECOGNIZER_EXAMPLE); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { //use a switch statement for more than one request code check if (requestCode==RECOGNIZER_EXAMPLE && resultCode==RESULT_OK) { // returned data is a list of matches to the speech input ArrayList<String> result = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); //display on screen tv.setText(result.toString()); } super.onActivityResult(requestCode, resultCode, data); } }
Recipe: Implementing a List of Choices
A common situation in applications is to provide a user with a list of choices that can be selected by clicking them. This can be easily implemented utilizing ListActivity, a subclass of Activity, and triggering an event based on what choice was made.
The steps for creating a list of choices are
- Create a class that extends the ListActivity class instead of the Activity class:
public class ActivityExample extends ListActivity { //content here }
- Create a String array of labels for each choice:
static final String[] ACTIVITY_CHOICES = new String[] { "Action 1", "Action 2", "Action 3" };
- Call setListAdapter() with the ArrayAdapter specifying this list and a layout:
setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, ACTIVITY_CHOICES)); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); getListView().setTextFilterEnabled(true);
- Launch an OnItemClickListener to determine which choice was selected and act accordingly:
getListView().setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { switch(arg2) {//extend switch to as many as needed case 0: //code for action 1 break; case 1: //code for action 2 break; case 2: //code for action 3 break; default: break; } } });
This technique is utilized in the next recipe.
Recipe: Using Implicit Intents for Creating an Activity
Implicit intents do not specify an exact component to use. Instead, they specify the functionality required through a filter, and the Android system must determine the best component to utilize. An intent filter can be either an action, data, or a category.
The most commonly used intent filter is an action, and the most common action is ACTION_VIEW. This mode requires a uniform resource identifier (URI) to be specified and then displays the data to the user. It does the most reasonable action for the given URI. For example, the implicit intents in case 0, 1, and 2 in the following example have the same syntax but produce different results.
The steps for launching an activity using an implicit intent are
- Declare the intent with the appropriate filter specified (ACTION_VIEW, ACTION_WEB_SEARCH, and so on).
- Attach any extra information to the intent required to run the activity.
- Pass this intent to startActivity().
This is shown for multiple intents in Listing 2.15.
Listing 2.15. src/com/cookbook/implicit_intents/ListActivityExample.java
package com.cookbook.implicit_intents; import android.app.ListActivity; import android.app.SearchManager; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.AdapterView.OnItemClickListener; public class ListActivityExample extends ListActivity { static final String[] ACTIVITY_CHOICES = new String[] { "Open Website Example", "Open Contacts", "Open Phone Dialer Example", "Search Google Example", "Start Voice Command" }; final String searchTerms = "superman"; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, ACTIVITY_CHOICES)); getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); getListView().setTextFilterEnabled(true); getListView().setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { switch(arg2) { case 0: //opens web browser and navigates to given website startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.android.com/"))); break; case 1: //opens contacts application to browse contacts startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("content://contacts/people/"))); break; case 2: //opens phone dialer and fills in the given number startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("tel:12125551212"))); break; case 3: //search Google for the string Intent intent= new Intent(Intent.ACTION_WEB_SEARCH ); intent.putExtra(SearchManager.QUERY, searchTerms); startActivity(intent); break; case 4: //starts the voice command startActivity(new Intent(Intent.ACTION_VOICE_COMMAND)); break; default: break; } } }); } }
Recipe: Passing Primitive Data Types Between Activities
Sometimes data needs to be passed to a launched activity. Sometimes a launched activity creates data that needs to be passed back to the calling activity. For example, a final score of a game needs to be returned to a high-scores screen. The different ways to pass information between activities are
- Declare the relevant variable in the calling activity (for example, public int finalScore) and set it in the launched activity (for example, CallingActivity.finalScore=score).
- Attach extras onto Bundles (demonstrated here).
- Use Preferences to store data to be retrieved later (covered in Chapter 5, "User Interface Events").
- Use the SQLite database to store data to be retrieved later (covered in Chapter 9).
A Bundle is a mapping from String values to various parcelable types. It can be created by adding extras to an intent. The following example shows data being passed from the main activity to the launched activity, where it is modified and passed back.
The variables (in this case, an integer and a String) are declared in the StartScreen activity. When the intent is created to call the PlayGame class, these variables are attached to the intent using the putExtra method. When the result is returned from the called activity, the variables can be read using the getExtras method. These calls are shown in Listing 2.16.
Listing 2.16. src/com/cookbook/passing_data_activities/StartScreen.java
package com.cookbook.passing_data_activities; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class StartScreen extends Activity { private static final int PLAY_GAME = 1010; private TextView tv; private int meaningOfLife = 42; private String userName = "Douglas Adams"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); tv = (TextView) findViewById(R.id.startscreen_text); //display initial values tv.setText(userName + ":" + meaningOfLife); //setup button listener Button startButton = (Button) findViewById(R.id.play_game); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { startGame(); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PLAY_GAME && resultCode == RESULT_OK) { meaningOfLife = data.getExtras().getInt("returnInt"); userName = data.getExtras().getString("userName"); //show it has changed tv.setText(userName + ":" + meaningOfLife); } super.onActivityResult(requestCode, resultCode, data); } private void startGame() { Intent launchGame = new Intent(this, PlayGame.class); //passing information to launched activity launchGame.putExtra("meaningOfLife", meaningOfLife); launchGame.putExtra("userName", userName); startActivityForResult(launchGame, PLAY_GAME); } }
The variables passed into the PlayGame activity can be read using the getIntExtra and getStringExtra methods. When the activity finishes and prepares an intent to return, the putExtra method can be used to return data back to the calling activity. These calls are shown in Listing 2.17.
Listing 2.17. src/com/cookbook/passing_data_activities/PlayGame.java
package com.cookbook.passing_data_activities; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; public class PlayGame extends Activity { private TextView tv2; int answer; String author; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.game); tv2 = (TextView) findViewById(R.id.game_text); //reading information passed to this activity //Get the intent that started this activity Intent i = getIntent(); //returns -1 if not initialized by calling activity answer = i.getIntExtra("meaningOfLife", -1); //returns [] if not initialized by calling activity author = i.getStringExtra("userName"); tv2.setText(author + ":" + answer); //change values for an example of return answer = answer - 41; author = author + " Jr."; //setup button listener Button startButton = (Button) findViewById(R.id.end_game); startButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { //return information to calling activity Intent i = getIntent(); i.putExtra("returnInt", answer); i.putExtra("returnStr", author); setResult(RESULT_OK, i); finish(); } }); } }