Multiple Activities
java -> android -> multiple activities ...
An application is a collection of tasks, each of which is handled by a separate, small, well-defined activity. Thus, a single application consists of a collection of interconnected activities. For example a game could consist of six activities: a splash screen, a main menu screen, a set-options screen, a game-play screen, and a save-game screen, and a load-game screen. Each "screen" is controlled by its own activity.
Android maintains a stack of activities:
- only the activity on top of the stack is visible to the user
- every time a new activity is started, it is pushed on top of the stack
- when an activity finishes (e.g. by pressing the device's BACK button) it is removed from the stack
- when the user long-presses the HOME key, the activity selected is moved to the top of the stack
If you start an activity, it becomes visible because it is put on top of the stack. If your activity in turn starts another activity, that one is put on top and the original activity is right below. When the top activity quits, it is removed from the stack and the original one becomes visible again. That simple principle allows an easy and (usually) user-predictable transitions from one activity to the next.
There are three primary ways to start an activity, other than the obvious one where the user picks it from the home screen.
One activity calls another inside its package or project
To start a secondary activity, the parent activity calls:
startActivity(new Intent(this, NewActivity.class));
This will put the secondary activity on top of the stack, but as a child of the calling (parent) activity. For this to work the activity NewActivity must be present in the same package as the calling one, and it must have an appropriate entry in the manifest: inside the application tag, put as a minimum
<activity android:name="NewActivity" </activity>
The "inner" activity does not necessarily need its own icon or intent filter as the main activity does. The easiest way to do this is to edit the AndroidManifest.xml in graphics mode, switch to the "Application" tag, scroll down and "add" an appropriate new Activity. Note that the Intent in the method call could optionally include data to be passed to the child activity.
Example: Here are two activities. The first one is the main project activity, but the second one also has an entry in the manifest. The first activity is simply a button, which when clicked opens the second activity, which in turn consists of a simple editable text field. It does not need a "quit" button because the user can just click the deice BACK button (but you might add a "back" button to aid the user). For simplicity we get by without an XML layout file. We need three things: two source code classes (one per activity) and one manifest file:
Create a new project named "FirstTwoApps". Copy the default source code file FirstTwoApps.java to a new file SecondAct.java. Then edit the files as follows:
SecondAct.java
public class SecondAct extends Activity { private EditText input = null; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // initializing the field input = new EditText(this); input.setText("Enter text here ..."); // defining the layout in code instead of XML LinearLayout layout = new LinearLayout(this); layout.addView(input, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); // setting the view to our layout setContentView(layout); } }
FirstTwoApp.java
public class FirstTwoApps extends Activity { private class ButtonHandler implements View.OnClickListener { public void onClick(View v) { handleButtonClick(); } } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button startAct = new Button(this); startAct.setText("Start Activity"); // defining the layout in code instead of XML LinearLayout layout = new LinearLayout(this); layout.addView(startAct, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); // activating the button startAct.setOnClickListener(new ButtonHandler()); // setting the view to our layout setContentView(layout); } private void handleButtonClick() { startActivity(new Intent(this, SecondAct.class)); } }
The call to start the secondary activity occurs in the private method handleButtonClick.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mathcs.twoapps" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".FirstTwoApps" 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="SecondAct"> </activity> </application> <uses-sdk android:minSdkVersion="4" /> </manifest>
When you launch the first activity it will display a single button. When you click it, the secpnd activity is launched, displaying an input box. You return to the first activity by clicking BACK. Explore the following:
- Start the activity, click the button to start the second activity, then press HOME to go to the home screen. Now long-press HOME. Which activity will be selectable, and which will show up when selected? Explain.
- Start the activity, click the button to start the second one. Type something in the text field. Press BACK, then start the second activity again. What's in the text field? Explain.
One activity calls another and expects a result
To start a secondary activity and expect a return notification when it is done, the parent activity calls:
startActivityForResult(Intent intent, int requestCode);
similar to before, but with a second integer parameter identifying the call. The result will come back through the inherited method in the parent activity:
onActivityResult(int requestCode, int resultCode, Intent data)
Just before the secondary activity exits, it should call setResult(int resultCode) or setResult(int resultCode, Intent data) to set the resultCode that the parent will pick up in onActivityResult as well as any data that should be returned. The result code is usually either RESULT_CANCELED or RESULT_OK, but could be any custom value starting at RESULT_FIRST_USER. The data Intent can be used to return any additional data it wants, and even additional information such as who can manipulate that data. We will get into details about an Intent soon, for now we will just use it to transport data. All of this information, the resultCode and the data appears automatically back in the parent's onActivityResult, along with the requestCode originally supplied so that you could distinguish which child activity is returning data to you in case you start more than one activity. Note that when the child activity terminates because of the user pressing th BACK button, the resultCode is automatically set to RESULT_CANCELED. This all may be a little confusing so here is the short summary.
- Each activity is defined in its own source code extending Activity, and each needs an entry in the manifest
- The primary activity:
- calls startActivityForResult to start a secondary activity
- overrides onActivityResult to receive feedback from the secondary activity
- The child activity:
- calls setResult to set the code and data to be returned if necessary; this should be done as soon as the data has changed in the child and prior to calling, for example, finish() to close the child activity
- if the user presses the BACK button the return code is automatically set to "canceled"; no call to setResult is necessary
Example: We'll use the previous activities FirstTwoApp and SecondAct. The second activity gets an "Okay" button in addition to the text field. If the user clicks on that button, the secondary activity should set its return data to the string entered, its return code to "okay", and close. The parent activity should display what happened in its child activity via a toast, i.e. it should receive the text the user entered in the child activity. Note that the manifest file already includes a reference to both activities, so we are fine there (otherwise we'd have to add an activity tag to the manifest).
First let's change the method handleButtonClick in FirstTwoApp to call startActivityForResult instead of startActivity. We also override onActivityResult to potentially receive and interpret any results:
private void handleButtonClick() { startActivityForResult(new Intent(this, SecondAct.class), INPUT_REQUEST); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_CANCELED) Toast.makeText(this, "activity canceled", Toast.LENGTH_SHORT).show(); else if (resultCode == Activity.RESULT_OK) Toast.makeText(this, "activity ok", Toast.LENGTH_SHORT).show(); }
Here INPUT_REQUEST is an integer constant that we define as 0, or really any value (code not listed). This will already work without any changes to SecondAct - try it. When the child activity is done (via the BACK button) the parent shows the answer as "canceled".
Next we add an Okay button with appropriate handler to the SecondAct class, where we set the return result to OK and close the activity when the button is clicked:
public class SecondAct extends Activity { private EditText input = null; private Button okay = null; private class ButtonOkayHandler implements View.OnClickListener { public void onClick(View v) { handleOkayButton(); } } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // initializing the fields input = new EditText(this); input.setText("Enter text here ..."); okay = new Button(this); okay.setText("Okay"); // defining the layout in code instead of XML LinearLayout layout = new LinearLayout(this); layout.setOrientation(LinearLayout.VERTICAL); layout.addView(input, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); layout.addView(okay, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); // activating the button okay.setOnClickListener(new ButtonOkayHandler()); // setting the view to our layout setContentView(layout); } private void handleOkayButton() { setResult(Activity.RESULT_OK); finish(); } }
This would pass different return codes to the parent (try it) but no extra data. So, we finally use an intent to transfer data from the text field of the second activity to the parent activity and display it in a toast (unless the secondary activity was canceled). In the SecondAct class we create a new Intent, set its "extra" fields to whatever was entered in the input text field (as a String!), and add that intent to the returned results via the second version of setResult.
Note that an Intent has many "putExtra" methods, one for every basic data type and arrays, and for each there is a corresponding "getXXXExtra". Specifically we change handleOkayButton in SecondAct to:
private void handleOkayButton() { Intent data = new Intent(); data.putExtra("input", input.getText().toString()); setResult(Activity.RESULT_OK, data); finish(); }
And on the other side in FirstTwoApp we change handleButton to:
protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_CANCELED) Toast.makeText(this, "activity canceled", Toast.LENGTH_SHORT).show(); else if (resultCode == Activity.RESULT_OK) { String input = data.getStringExtra("input"); Toast.makeText(this, "input = " + input, Toast.LENGTH_SHORT).show(); } }
An activity broadcasts a request to the OS to start a suitable activity
The third way to start a child activity is to define a suitable Intent object and broadcast it system-wide so that it can be picked up by whatever activity is capable of handling that intent. We can for example create an intent with a phone number ad a request to dial it, broadcast it and let the system figure out who exactly will handle the dialing. This idea, actually, is one of higlights For that to work we need to request permission in the manifest to use such system activities via a "uses-permission" entry.
We will pick this up in the next section!