The Android Developer’s Cookbook: Building Applications with the Android SDK, Second Edition (2013)
Chapter 2. Application Basics: Activities and Intents
Each Android application is represented by a single Android project. An overview of the project structure, including a brief introduction to the basic building blocks of an application, is provided as useful background information for the recipes in this book. Then the focus of this chapter turns to activities and the intents that launch them.
Android Application Overview
An Android application consists of various functionalities. Some examples are editing a note, playing a music file, ringing an alarm, or opening a phone contact. These functionalities can be classified into four different Android components, shown in Table 2.1, each of which is specified by a Java base class.
Table 2.1 Possible Components of an Android Application
Every application is made up of one or more of these components. They are instantiated by the Android OS as needed. Other applications are allowed to use them, too, within the specified permissions.
As multiple functionalities play out in the OS (some not even related to the intended application, such as an incoming phone call), each component goes through a lifecycle of getting created, focused, defocused, and destroyed. The default behavior can be overridden for a graceful operation, such as saving variables or restoring UI elements.
With the exception of ContentProvider, each component is activated by an asynchronous message called an Intent. The Intent can contain a Bundle of supporting information describing the component. This provides a method of passing information between components.
The rest of this chapter demonstrates the previous concepts using the most common component: the Activity. Because activities almost always specify an interaction with a user, a window is automatically created with each activity. Therefore, a short introduction to the UI is also included. Of the other components, Service and BroadcastReceiver are covered in Chapter 3, “Threads, Services, Receivers, and Alerts,” and ContentProvider is covered in Chapter 11, “Data Storage Methods.”
Recipe: Creating a Project and an Activity
A straightforward way to create an Android project or any of its components is to use the Eclipse IDE. This method ensures proper setup of the supporting files. The steps to create a new Android project are:
1. In Eclipse, choose File → New → Android Application Project. This displays a New Android Project creation screen.
2. Fill in the project name, such as SimpleActivityExample.
3. Fill in the application name, such as Example of Basic Activity.
4. Fill in the package name, such as com.cookbook.simpleactivity.
5. Select a minimum required SDK. This will be the lowest Android version the app can run on. Choosing at least API Level 8 or Android 2.2 is recommended.
6. Select a build target SDK from the choices provided. Choose the highest Android version that will be tested against.
7. Choose the SDK version the app will be compiled against. This should be the latest version available or the latest version required by the libraries.
8. Choose the base theme of the application. The theme can be edited or changed later if desired; this is just meant to be a good start.
9. Next, configure some more project defaults. Check Create custom launcher icon to replace the default icon now. To create the main activity in one of the next steps, be sure Create Activity is checked.
10. In the Configure Launcher Icon screen, choose among text, clip art from a small library, or an image from the hard drive. The resulting image will be created in all four standard resolutions.
11. To create the main activity in the same step, be sure Create Activity is checked and select BlankActivity. The use of fragments will be shown in a later recipe.
12. Fill in activity and layout names or leave them as is. To use one of the default navigation patterns, a minimum SDK version of 14 is required, as they rely on the ActionBar.
13. Press Finish to create the sample project.
All activities extend the abstract class Activity or one of its subclasses. The entry point to each activity is the onCreate() method. It is almost always overridden to initialize the activity, such as setting up the UI, creating button listeners, initializing parameters, and starting threads.
If the main activity is not created with the project or another activity needs to be added, the steps to create an activity are:
1. Create a class to extend Activity. In Eclipse, this can be done by right-clicking on the project, choosing New → Class, and then specifying android.app.Activity as the superclass.
2. Override the onCreate() function. In Eclipse, this can be done by right-clicking on the class file, choosing Source → Override/Implement Methods..., and then checking the onCreate() method. As with most overridden functions, the overwritten onCreate() method must invoke the superclass method, too; otherwise, an exception may be thrown at run-time. Here, super.onCreate() should be called first to properly initialize the activity, as shown in Listing 2.1.
Listing 2.1. src/com/cookbook/simple_activity/SimpleActivity.java
package com.cookbook.simple_activity;
import android.app.Activity;
import android.os.Bundle;
public class SimpleActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
3. If a UI is used, specify the layout in an XML file in the res/layout/ directory. Here, it is called main.xml, as shown in Listing 2.2.
4. Set the layout of the activity using the setContentView() function and passing it the resource ID for the XML layout file. Here, it is R.layout.main, as shown in Listing 2.1.
Listing 2.2. 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="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>
5. Declare the properties of the activity in the AndroidManifest.xml file. This is covered in more detail later in Listing 2.5.
Note that the string resources are defined in the strings.xml file in the res/values/ folder, as shown in Listing 2.3. This provides a central place for all strings in case text needs to be changed or reused.
Listing 2.3. res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Hello World, SimpleActivity!</string>
<string name="app_name">SimpleActivity</string>
</resources>
Now the directory structure of this project and the additional autogenerated content are explored in more detail.
Directory Structure of Project and Autogenerated Content
The project structure is a mix of user-generated and autogenerated files. Figure 2.1 shows an example project structure, as seen from the Eclipse Package Explorer.
Figure 2.1 Android project directory structure, as seen in the Eclipse IDE
User-generated files include the following:
src/ contains the Java packages the developer writes or imports for the application. Each package can have multiple .java files representing different classes.
res/layout/ contains the XML files that specify the layout of each screen.
res/values/ contains the XML files used as references by other files.
res/values-v11/ contains the XML files for Honeycomb devices and above.
res/values-v14/ contains the XML files for Ice Cream Sandwich and above.
res/drawable-xhdpi/, res/drawable-hdpi/, res/drawable-mdpi/, and res/drawable-ldpi/ are directories that contain pictures the application uses. They have extra-large, high, medium, and low dots-per-inch resolution, respectively.
assets/ contains additional nonmedia files the application uses.
AndroidManifest.xml specifies the project to the Android OS.
The styles.xml file is created in all of the res/values-XX folders. This is because the Android base theme changed to Holo starting with Honeycomb devices. It results in having different parent themes in an app theme.
Autogenerated files include these:
gen/ contains autogenerated code, including the generated class file R.java.
project.properties contains project settings. Although autogenerated, it should be kept under revision control.
An application’s resources include XML files describing the layout, XML files describing values such as strings, labels of UI elements, and additional supporting files such as pictures and sounds. At compile time, references to the resources are gathered into an autogenerated wrapper class called R.java. The Android Asset Packaging Tool (aapt) autogenerates this file. Listing 2.4 shows what it looks like for the “Creating a Project and an Activity” recipe.
Listing 2.4. gen/com/cookbook/simple_activity/R.java
/* AUTO-GENERATED FILE. DO NOT MODIFY.
*
* This class was automatically generated by the
* aapt tool from the resource data it found. It
* should not be modified by hand.
*/
package com.cookbook.simple_activity;
public final class R {
public static final class attr {
}
public static final class drawable {
public static final int icon=0x7f020000;
}
public static final class layout {
public static final int main=0x7f030000;
}
public static final class string {
public static final int app_name=0x7f040001;
public static final int hello=0x7f040000;
}
}
Here, each resource is mapped to a unique integer value. In this way, the R.java class provides a way to reference external resources within Java code. For example, to reference the main.xml layout file in Java, the R.layout.main integer is used. To reference the same within XML files, the"@layout/main" string is used.
Referencing resources from within Java or XML files is demonstrated in Table 2.2. Note that to define a new button ID called home_button, the plus sign is added to the identifying string: @+id/home_button. More complete details on resources are given in Chapter 5, “User Interface Layout,” but this suffices to cover the recipes in this chapter.
Table 2.2 Resources in Java and XML Files
Android Package and Manifest File
The Android project, sometimes also referred to as an Android package, is a collection of Java packages. Different Android packages can have the same Java package names, whereas the Android package name must be unique across all applications installed on the Android device.
For the OS to access them, each application must declare its available components in a single AndroidManifest.xml file. In addition, this file contains the required permissions and behavior for the application to run. Listing 2.5 shows what it looks like for the “Creating a Project and an Activity” recipe.
Listing 2.5. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cookbook.simple_activity"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".SimpleActivity"
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="8" />
</manifest>
The first line is required and standard across all XML files in Android to specify the encoding. The manifest element defines the Android package name and version. The versionCode is an integer that can be evaluated in programs to determine the upgrade or downgrade relationship. TheversionName represents a human-readable format that can have major and minor revisions declared.
The application element defines the icon and label the user sees from the Android device menu. The label is a string and should be short enough to display under the icon on a user’s device. Generally, the name can be up to two words of ten characters each without being cut off.
The activity element defines the main activity that is launched when the application is started and the name shown in the title bar when the activity is active. Here, the Java package name needs to be specified, which in this example is com.cookbook.simple_activity.SimpleActivity.Because the Java package name is usually the same as the Android package name, the shorthand notation is often used: .SimpleActivity. However, it is best to remember that the Android package and the Java package are distinct.
The intent-filter element informs the Android system of the capabilities of the component. It can have multiple action, category, or data elements for this purpose. This element is used in different recipes in this book.
The uses-sdk element defines the API level required to run the application. In general, the API level is specified as follows:
<uses-sdk android:minSdkVersion="integer"
android:targetSdkVersion="integer"
android:maxSdkVersion="integer" />
Because the Android OS is constructed to be forward compatible, maxSdkVersion is highly discouraged and not even adhered to on devices with Android 2.0.1 or later. Google Play, however, continues to use this as a filter, so the application is not shown for download to devices running a higher SDK version.
Specifying targetSdkVersion is not required, but it allows devices of the same SDK version to disable compatibility settings that might speed up operation. minSdkVersion should always be specified to ensure that the application does not crash when run on a platform that does not support the required features in the application. Always choose the lowest API level possible when specifying this.
The AndroidManifest.xml file can also contain permission settings needed to run the application. More complete details about the options are provided in later chapters, but this suffices to cover the recipes in this chapter.
Recipe: Renaming Parts of an Application
Sometimes a portion of an Android project needs to be renamed. Maybe a file was copied manually into the project, such as from this book. Maybe the application name has changed during development, and the change needs to be reflected in the filesystem tree. Automatic tools help with this and ensure that cross-references are automatically updated. For example, in the Eclipse IDE, there are different ways to rename portions of an application:
Rename the Android project, as follows:
1. Right-click the project and Refactor → Move to a new directory in the filesystem.
2. Right-click the project and Refactor → Rename the project.
Rename the Android package, as follows:
1. Right-click the package and Refactor → Rename the package.
2. Edit the AndroidManifest.xml file to ensure that the new package name is reflected.
Rename an Android class (such as the major components Activity, Service, BroadcastReceiver, ContentProvider) as follows:
1. Right-click the .java file and Refactor → Rename the class.
2. Edit the AndroidManifest.xml file to ensure that android:name has the new component name.
Note that renaming other files, such as XML files, usually requires manually changing the corresponding references in the Java code.
Recipe: Using a Library Project
Library projects allow the reuse of resources and code in other applications. They are also used for UI libraries that enable modern features on older devices. Library projects were introduced with release 14 of the SDK tools. Library projects are very similar to normal Android projects, as they too have source and resources folders and a manifest file. The main difference is that they cannot run on their own, and they cannot be compiled into an .apk file. To create a library project, do the following:
1. In Eclipse, choose File → New → Android Application Project. This displays a New Android Project creation screen.
2. Fill in the project name, such as SimpleLibraryExample.
3. Fill in the application name, such as Example of Basic Activity.
4. Fill in the package name, such as com.cookbook.simplelibrary.
5. Select a build target from the choices provided. These choices are based on the SDK versions that are installed on the development computer.
6. Uncheck Create custom launcher icon, as a library does not need one.
7. Check Mark this project as a Library.
8. To have activities in the library, check Create Activity. The created activity will be used in the main project; check this and select BlankActivity for now.
9. Fill in an activity name or leave it as is.
10. Change the layout name to lib_activity_main. As all resources are compiled into one R class in the end, it is best practice to give all resources of a library a prefix to avoid name conflicts.
11. Press Finish to create the library project.
To use the library, a primary or main project is needed. Follow the “Creating a Project and an Activity” recipe to add one to the workspace, with a minor modification: Do not create an activity. Instead, use the activity from the library project.
To reference a library project from the main project, do the following:
1. In Eclipse, right-click on the project and choose Properties → Android.
2. Scroll down to the library section and press Add.
3. The dialog will show all available library projects in the workspace. Select the SimpleLibrary project and press OK.
The project name and path reference are now shown in the Properties page. There is a green arrow indicating that the reference was checked as OK. If the referenced path cannot be found, a red cross will be shown instead.
After adding a library project, it is recommended to make a clean and full build of the workspace to make sure the changes worked as expected.
Internally, library references are saved in the project.properties file, which is used by both Eclipse and Ant. The resulting file should now look like this:
target=android-16
android.library.reference.1=../SimpleLibrary
Listing 2.6 adds the activity from the library to the AndroidManifest.xml file and makes it the default launcher activity.
Listing 2.6. AndroidManifest.xml of the Main Project
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.simpleproject"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
<application android:label="@string/app_name"
android:icon="@drawable/ic_launcher"
android:theme="@style/AppTheme">
<activity
android:name="com.cookbook.simplelibrary.LibMainActivity"
android:label="@string/title_activity_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Note here that the package name of the application is completely different from the package name of the activity. Because the activity is loaded from the library project, its name must be set to a full qualified package and class name.
The project can now be run from within Eclipse; the activity from the library will come to the front.
There are many use cases for library projects, from white-label apps that set different themes in the main project but run on the same code base to using UI libraries like ActionBarSherlock to support a modern look and feel on older Android devices.
Activity Lifecycle
Each activity in an application goes through its own lifecycle. Once and only once when an activity is created is the onCreate() function executed. If the activity exits, the onDestroy() function is executed. In between, various events can lead to the activity being in multiple states, as illustrated in Figure 2.2. The next recipe provides an example of each of these functions.
Figure 2.2 Activity lifecycle from http://developer.android.com/
Recipe: Using Activity Lifecycle Functions
This recipe provides a simple way to see the activity lifecycle in action. For illustration purposes, each overridden function is explicit, and a Toast command is added to show on screen when the function is entered (more detail on the Toast widget is provided in Chapter 3, “Threads, Services, Receivers, and Alerts”). The activity is shown in Listing 2.7. Run it on an Android device and try various cases. In particular, note the following:
Changing the screen orientation destroys and re-creates the activity from scratch.
Pressing the Home key pauses the activity but does not destroy it.
Pressing the application icon might start a new instance of the activity, even if the old one was not destroyed.
Letting the screen sleep pauses the activity, and the screen awakening resumes it. (This is similar to taking an incoming phone call.)
As seen here, various common user actions can cause the activity to be paused or killed, or can even launch multiple versions of the application. Before moving on, it is worth mentioning two additional simple recipes that can control this behavior.
Recipe: Forcing Single Task Mode
Navigating away from an application and launching it again can lead to multiple instances of an activity on the device. Eventually the redundant instance of the activity is killed to free up memory, but in the meantime it can lead to odd situations. To avoid these, the developer can control this behavior for each activity in the AndroidManifest.xml file.
To ensure that only one instance of the activity runs on the device, specify the following in an activity element that has the MAIN and LAUNCHER intent filters:
android:launchMode="singleInstance"
This keeps a single instance of each activity in a task at all times. In addition, any child activity is launched as its own task. To further ensure that there is only a single task for all activities of an application, use the following:
android:launchMode="singleTask"
Listing 2.7. src/com/cookbook/activity_lifecycle/ActivityLifecycle.java
package com.cookbook.activity_lifecycle;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
public class ActivityLifecycle extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();
}
@Override
protected void onStart() {
super.onStart();
Toast.makeText(this, "onStart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "onResume", Toast.LENGTH_SHORT).show();
}
@Override
protected void onRestart() {
super.onRestart();
Toast.makeText(this, "onRestart", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPause() {
Toast.makeText(this, "onPause", Toast.LENGTH_SHORT).show();
super.onPause();
}
@Override
protected void onStop() {
Toast.makeText(this, "onStop", Toast.LENGTH_SHORT).show();
super.onStop();
}
@Override
protected void onDestroy() {
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
super.onDestroy();
}
}
This allows the activities to share information easily as the same task.
In addition, it might be desirable to retain the task state, regardless of how a user navigates to the activity. For example, if a user leaves the application and relaunches it later, the default behavior often resets the task to its initial state. To ensure that the task is always in its last state when the user returns, specify the following in the activity element of the root activity of a task:
android:alwaysRetainTaskState="true"
Recipe: Forcing Screen Orientation
Any Android device with an accelerometer can determine which way is down. As the device is tilted from portrait to landscape mode, the default action is to rotate the application view accordingly. However, as seen in the “Using Activity Lifecycle Functions” recipe, the activity is destroyed and restarted upon screen orientation changes. When this happens, the current state of the activity might be lost, disrupting the user experience.
One option to handle screen orientation changes gracefully is to save state information before the change and restore information after the change. A simpler method that might be useful is to force the screen orientation to stay constant. For each activity in the AndroidManifest.xml file, the screenOrientation attribute can be specified. For example, to specify that the activity always stays in portrait mode, the following can be added to the activity element:
android:screenOrientation="portrait"
Similarly, landscape mode can be specified using the following:
android:screenOrientation="landscape"
However, this code still causes the activity to be destroyed and restarted when a hard keyboard is slid out. Therefore, a third method is possible: Tell the Android system that the application should handle orientation and keyboard slide-out events. This is done by adding the following attribute to the activity element:
android:configChanges="orientation|keyboardHidden"
This can be used alone or in combination with the screenOrientation attribute to specify the required behavior to the application.
Recipe: Saving and Restoring Activity Information
Whenever an activity is about to be killed, the onSaveInstanceState() function is called. Override this to save relevant information that should be retained. When the activity is then re-created, the onRestoreInstanceState() function is called. Override this function to retrieve the saved information. This allows for a seamless user experience when an application undergoes lifecycle changes. Note that most UI states do not need to be managed because they are, by default, taken care of by the system.
OnSaveInstanceState() is distinct from onPause(). For example, if another component is launched in front of the activity, the onPause() function is called. Later, if the activity is still paused when the OS needs to reclaim resources, it calls onSaveInstanceState() before killing the activity.
An example of saving and restoring the instance state consisting of a string and a float array is shown in Listing 2.8.
Listing 2.8. Example of onSaveInstanceState() and onRestoreInstanceState()
float[] localFloatArray = {3.14f, 2.718f, 0.577f};
String localUserName = "Euler";
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Save the relevant information
outState.putString("name", localUserName);
outState.putFloatArray("array", localFloatArray);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
//Restore the relevant information
localUserName = savedInstanceState.getString("name");
localFloatArray = savedInstanceState.getFloatArray("array");
}
Note that onCreate() also contains Bundle savedInstanceState. In the case of an activity reinitializing after previously being shut down, the bundle saved in onSaveInstanceState() is also passed to onCreate(). In all cases, the saved bundle is passed to the onRestoreInstanceState()function, so it is more natural to use this to restore states.
Recipe: Using Fragments
Fragments are the latest addition to the basic building blocks of an Android application. Fragments are smaller parts of an activity meant for the grouping of views and functionalities. The best analogy for them is to think of smaller building blocks that can be stacked on one another to fill the volume of a bigger block. The need for smaller blocks arose from the introduction of tablets and TV screens.
Fragments allow views to be bundled together and to be mixed and matched within one or two (or even more) activities as needed. The classic use case for this is going from landscape mode with a list and a detail view to portrait mode with a single list and a detail screen. In fact, this pattern has become so mainstream that it is now possible to create a skeleton app like this directly from the Create New Project dialog.
The steps to do this are similar to the ones described in the previous recipe:
1. In Eclipse, choose File → New → Android Application Project.
2. Fill in the project name, such as SimpleFragmentExample.
3. Fill in the application name, such as Example of Basic Fragments.
4. Fill in the package name, such as com.cookbook.simplefragments.
5. Select a minimum required SDK of API Level 11 or Android Honeycomb. Fragments can be used in lower API versions only if the support library extra is installed on the machine.
6. In the Create Activity screen, choose MasterDetailFlow as the start point.
7. Name the items used for demo purposes; for instance, fruits.
8. Press Finish to create the sample project.
Exploring the possibilities of this sample is left to the reader. Here, instead, a few important things about fragments are highlighted.
Fragments come with their own lifecycle, which is dependent on the hosting activities. As fragments can be added, shown, hidden, and removed at any time during the lifecycle of an activity, their existence is much more short-lived than that of other components. Similar to an activity, a fragment has onPause(), onResume(), onDestroy(), and onCreate() methods.
It is to be noted, however, that onCreate(Bundle) is the second method called on a fragment; the first one is onAttach(Activity), which signals that there is a connection to the hosting activity now. Methods can be called on the activity here; however, it is not guaranteed that the activity has been fully initialized itself. Only after onActivityCreated() is called is the activity passed through its own onCreate() method.
Given that fragments can be instantiated and added at much later times, the state of the activity in onAttach() should not be relied upon. The method used to initialize views and start most work is onCreateView(LayoutInflater, ViewGroup, Bundle). The Bundle class given here is the saved instance state, if the fragment is re-created.
Fragments use bundles also for serializing arguments. Every parcelable type of external information a fragment needs can be obtained from the hosting activity by calling setArguments() and can always be read in the fragments with the getArguments() call. This allows information coming from the starting intent of the activity to be passed directly to the fragment to be shown.
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.xml file.
Android uses 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 for adding a button to a given layout and assigning an action to a button press follow:
1. 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" />
2. Declare a button that points to the button ID in the layout file:
Button startButton = (Button) findViewById(R.id.trigger);
3. Specify a listener for when the button is clicked:
//Set up button listener
startButton.setOnClickListener(new View.OnClickListener() {
//Insert onClick here
});
4. 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 follow:
1. 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="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
2. Declare a TextView that points to the TextView ID in the layout file:
private TextView tv = (TextView) findViewById(R.id.hello_text);
3. 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 5, “User Interface Layout.”
Recipe: Launching a Second Activity from an Event
In this recipe, MenuScreen is the main activity, as shown in Listing 2.9. 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 and launches the PlayGame activity. When a user clicks the button in the PlayGame activity, it calls finish() to return control to the calling activity. Following are the steps for launching an activity:
1. Declare an intent that points to the activity to be launched.
2. Call startActivity on this intent.
3. Declare the additional activity in the AndroidManifest.xml file.
Listing 2.9. 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);
//Set up 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);
}
}
Providing Current Context in an Anonymous Inner Class
Note the additional consideration needed for launching an activity with a button press, as shown in Listing 2.9. The intent needs a context. However, using the this shortcut in the onClick function is not properly resolved. Following are different ways to provide current context in an anonymous inner class:
Use Context.this instead of this.
Use getApplicationContext() instead of this.
Explicitly use the class name MenuScreen.this.
Call a function that is declared at the right context level. This is what is used in Listing 2.8: startGame().
These methods are usually interchangeable. Use the one that works best for the situation.
The PlayGame activity shown in Listing 2.10 is simply a button with an 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 lead to their own finish() calls.
Listing 2.10. 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);
//Set up 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.11, with the ID play_game to match what was declared in Listing 2.9. Here, the size of the button is also declared in device-independent pixels (dip), which are discussed more in Chapter 5, “User Interface Layout.”
Listing 2.11. 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="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_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.12.
Listing 2.12. 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="match_parent"
android:layout_height="match_parent"
>
<Button android:id="@+id/end_game"
android:layout_width="100dip" android:layout_height="100dip"
android:text="@string/end_game" android:layout_gravity="center"
/>
</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.13.
Listing 2.13. 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.14.
Listing 2.14. 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 use speech-to-text functionality from Google’s RecognizerIntent activity 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 normally. The calling activity can have a problem and not return properly. Therefore, resultCode should always be checked to ensureRESULT_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 a result follow:
1. Call startActivityForResult() with an intent, defining the launched activity and an identifying requestCode variable.
2. Override the onActivityResult() function to check on the status of the result, check for the expected requestCode, and parse the returned data.
Following are the steps for using RecognizerIntent:
1. Declare an intent with action ACTION_RECOGNIZE_SPEECH.
2. 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.
3. 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.15.
The additional supporting files needed are main.xml and strings.xml, which need to define a button and the TextView to hold the result. This is accomplished using Listings 2.11 and 2.13 in the “Launching a Second Activity from an Event” recipe. The AndroidManifest.xml file needs to declare only the main activity, which is the same as in the earlier “Creating a Project and an Activity” recipe. The RecognizerIntent activity is native to the Android system and does not need to be declared explicitly to be used.
Listing 2.15. 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);
//Set up 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 using ListActivity, a subclass of Activity, and triggering an event based on what choice was made.
The steps for creating a list of choices follow:
1. Create a class that extends the ListActivity class instead of the Activity class:
public class ActivityExample extends ListActivity {
//content here
}
2. Create a string array of labels for each choice:
static final String[] ACTIVITY_CHOICES = new String[] {
"Action 1",
"Action 2",
"Action 3"
};
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);
4. Launch 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 used 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 use. An intent filter can be 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 cases 0, 1, and 2 in the following example have the same syntax but produce different results.
Following are the steps for launching an activity using an implicit intent:
1. Declare the intent with the appropriate filter specified (ACTION_VIEW, ACTION_WEB_SEARCH, and so on).
2. Attach any extra information to the intent required to run the activity.
3. Pass this intent to startActivity().
This is shown for multiple intents in Listing 2.16.
Listing 2.16. 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: //searches 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, the final score of a game needs to be returned to a high-scores screen. Following are different ways to pass information between activities:
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 in this recipe).
Use Preferences to store data to be retrieved later (covered in Chapter 6, “User Interface Events”).
Use the SQLite database to store data to be retrieved later (covered in Chapter 11, “Data Storage Methods”).
A Bundle is a mapping from string values to various parcelable types. It can be created by adding extras to an intent. This recipe 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.17.
Listing 2.17. 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);
//Set up 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("returnStr");
//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.18.
Listing 2.18. 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.";
//Set up 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();
}
});
}