Intents, Tasks, and Launch Modes
Up until now, you have been using the standard behavior for creating Activities and moving from one Activity to the next. The flow you have been using is the default, and in most cases, this will be the one you choose to use. When you open the app from the launcher with the default behavior, it creates its own Task, and each Activity you create is added to a back stack, so when you open three Activities one after the other as part of your user's journey, pressing the back button three times will move the user back through the previous screens/Activities and then go back to the device's home screen, while still keeping the app open.
The launch mode for this type of Activity is called Standard
; it is the default and doesn't need specifying in the Activity element of AndroidManifest.xml
. Even if you launch the same Activity three times, one after the other, there will be three instances of the same activity that exhibit the behavior described previously.
For some apps, you may want to change this behavior. The scenario most commonly used that doesn't conform to this pattern is when you want to relaunch an Activity without creating a new separate instance. A common use case for this is when you have a home screen with a main menu and different news stories that the user can read. Once the user has gone through to an individual news story and then presses another news story title from the menu, when the user presses the back button, they will expect to return to the home screen and not the previous news story. The launch mode that can help here is called singleTop
. If a singleTop
Activity is at the top of the Task (top, in this context, means most recently added), when the same singleTop
Activity is launched, then instead of creating a new Activity, it uses the same Activity and runs the onNewIntent
callback. In the preceding scenario, this could then use the same activity to display a different news story. In this callback, you receive an intent, and you can then process this intent as you have done previously in onCreate
.
There are two other launch modes to be aware of, called SingleTask
and SingleInstance
. These are not for general use and are only used for special scenarios. For both of these launch modes, only one Activity of this type can exist in the application and it is always at the root of its Task. If you launch an Activity with this launch mode, it will create a new Task. If it already exists, then it will route the intent through the onNewIntent
call and not create another instance. The only difference between SingleTask
and SingleInstance
is that SingleInstance
is the one and only Activity of its Task. No new Activities can be launched into its Task. In contrast, SingleTask
does allow other Activities to be launched into its Task, but the SingleTask
Activity is always at the root.
These launch modes can be added to the XML of AndroidManifest.xml
or created programmatically by adding intent flags. The most common ones used are the following:
FLAG_ACTIVITY_NEW_TASK
: Launches the Activity into a new Task.FLAG_ACTIVITY_CLEAR_TASK
: Clears the current Task, so finishes all Activities and launches the Activity at the root of the current Task.FLAG_ACTIVITY_SINGLE_TOP
: Replicates the launch mode of thelaunchMode="singleTop"
XML.FLAG_ACTIVITY_CLEAR_TOP
: Removes all Activities that are above any other instances of the same activity. If this is launched on a standard launch mode Activity, then it will clear the Task down to the first existing instance of the same Activity, and then launch another instance of the same Activity. This will probably not be what you want, and you can launch this flag with theFLAG_ACTIVITY_SINGLE_TOP
flag to clear all the activities down to the same instance of the Activity you are launching and not create a new instance, but instead route a new intent to the existing Activity. To create an Activity using these twointent
flags, you would do the following:val intent = Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP } startActivity(intent)
If an intent launches an Activity with one or more of the intent flags specified in the preceding code block, then the launch mode specified overrides the one that is set in the AndroidManifest.xml
file.
Intent flags can be combined in multiple ways. For more information, see the official documentation at https://developer.android.com/reference/android/content/Intent.
You'll explore the differences in the behavior of these two launch modes in the next exercise.
Exercise 2.06: Setting the Launch Mode of an Activity
This exercise has many different layout files and Activities to illustrate the two most commonly used launch modes. Please download the code from http://packt.live/2LFWo8t and then we will go through the exercise at http://packt.live/2XUo3Vk:
- Open up the
activity_main.xml
file and examine it.This illustrates a new concept when using layout files. If you have a layout file and you would like to include it in another layout, you can use the
<include>
XML element (have a look at the following snippet of the layout file):<include layout="@layout/letters" android:id="@+id/letters_layout" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/ launch_mode_standard"/> <include layout="@layout/numbers" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/ launch_mode_single_top"/>
The preceding layout uses the
include
XML element to include the two layout files:letters.xml
andnumbers.xml
. - Open up and inspect the
letters.xml
andnumbers.xml
files found in theres
|layout
folder. These are very similar and are only differentiated from the buttons they contain by the ID of the buttons themselves and the text label they display. - Run the app and you will see the following screen:
In order to demonstrate/illustrate the difference between
standard
andsingleTop
activity launch modes, you have to launch two or three activities one after the other. - Open up
MainActivity
and examine the contents of the code block inonCreate(savedInstanceState: Bundle?)
after the signature:val buttonClickListener = View.OnClickListener { view -> when (view.id) { R.id.letterA -> startActivity(Intent(this, ActivityA::class.java)) //Other letters and numbers follow the same pattern/flow else -> { Toast.makeText( this, getString(R.string.unexpected_button_pressed), Toast.LENGTH_LONG ) .show() } } } findViewById<View>(R.id.letterA).setOnClickListener(buttonClickListener) //The buttonClickListener is set on all the number and letter views }
The logic contained in the main Activity and the other activities is basically the same. It displays an Activity and allows the user to press a button to launch another Activity using the same logic of creating a ClickListener and setting it on the button you saw in Exercise 2.05, Retrieving a Result from an Activity.
- Open the
AndroidManifest.xml
file and you will see the following:<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.launchmodes"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.LaunchModes"> <activity android:name=".ActivityA" android:launchMode="standard"/> <activity android:name=".ActivityB" android:launchMode="standard"/> <activity android:name=".ActivityC" android:launchMode="standard"/> <activity android:name=".ActivityOne" android:launchMode="singleTop"/> <activity android:name=".ActivityTwo" android:launchMode="singleTop"/> <activity android:name=".ActivityThree" android:launchMode="singleTop"/> <activity android:name=".MainActivity"> <intent-filter> <action android:name= "android.intent.action.MAIN" /> <category android:name= "android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
You launch an Activity based on a button pressed on the main screen, but the letter and number activities have a different launch mode, which you can see specified in the
AndroidManifest.xml
file.The
standard
launch mode is specified here to illustrate the difference betweenstandard
andsingleTop
, butstandard
is the default and would be how the Activity is launched if theandroid:launchMode
XML attribute was not present. - Press one of the letters under the
Standard
heading and you will see the following screen (withA
or lettersC
orB
): - Keep on pressing any of the letter buttons, which will launch another Activity. Logs have been added to show this sequence of launching activities. Here is the log after pressing 10 letter Activities randomly:
2019-10-23 20:50:51.097 15281-15281/com.example.launchmodes D/MainActivity: onCreate 2019-10-23 20:51:16.182 15281-15281/com.example.launchmodes D/Activity B: onCreate 2019-10-23 20:51:18.821 15281-15281/com.example.launchmodes D/Activity B: onCreate 2019-10-23 20:51:19.353 15281-15281/com.example.launchmodes D/Activity C: onCreate 2019-10-23 20:51:20.334 15281-15281/com.example.launchmodes D/Activity A: onCreate 2019-10-23 20:51:20.980 15281-15281/com.example.launchmodes D/Activity B: onCreate 2019-10-23 20:51:21.853 15281-15281/com.example.launchmodes D/Activity B: onCreate 2019-10-23 20:51:23.007 15281-15281/com.example.launchmodes D/Activity C: onCreate 2019-10-23 20:51:23.887 15281-15281/com.example.launchmodes D/Activity B: onCreate 2019-10-23 20:51:24.349 15281-15281/com.example.launchmodes D/Activity C: onCreate
If you observe the preceding log, every time the user presses a character button in launch mode, a new instance of the character Activity is launched and added to the back stack.
- Close the app, making sure it is not backgrounded (or in the recents/overview menu) but is actually closed, and then open the app again and press one of the number buttons under the
Single Top
heading: - Press the number buttons 10 times, but make sure you press the same number button at least twice sequentially before pressing another number button.
The logs you should see in the
Logcat
window (View
|Tool Windows
|Logcat
) should be similar to the following:2019-10-23 21:04:50.201 15549-15549/com.example.launchmodes D/MainActivity: onCreate 2019-10-23 21:05:04.503 15549-15549/com.example.launchmodes D/Activity 2: onCreate 2019-10-23 21:05:08.262 15549-15549/com.example.launchmodes D/Activity 3: onCreate 2019-10-23 21:05:09.133 15549-15549/com.example.launchmodes D/Activity 3: onNewIntent 2019-10-23 21:05:10.684 15549-15549/com.example.launchmodes D/Activity 1: onCreate 2019-10-23 21:05:12.069 15549-15549/com.example.launchmodes D/Activity 2: onNewIntent 2019-10-23 21:05:13.604 15549-15549/com.example.launchmodes D/Activity 3: onCreate 2019-10-23 21:05:14.671 15549-15549/com.example.launchmodes D/Activity 1: onCreate 2019-10-23 21:05:27.542 15549-15549/com.example.launchmodes D/Activity 3: onNewIntent 2019-10-23 21:05:31.593 15549-15549/com.example.launchmodes D/Activity 3: onNewIntent 2019-10-23 21:05:38.124 15549-15549/com.example.launchmodes D/Activity 1: onCreate
You'll notice that instead of calling onCreate
when you pressed the same button again, the Activity is not created, but a call is made to onNewIntent
. If you press the back button, you'll notice that it will take you less than 10 clicks to back out of the app and return to the home screen, reflecting the fact that 10 activities have not been created.
Activity 2.01: Creating a Login Form
The aim of this activity is to create a login form with username and password fields. Once the values in these fields are submitted, check these entered values against hardcoded values and display a welcome message if they match, or an error message if they don't, and return the user to the login form. The steps needed to achieve this are the following:
- Create a form with username and password
EditText
Views and aLOGIN
button. - Add a
ClickListener
interface to the button to react to a button press event. - Validate that the form fields are filled in.
- Check the submitted username and password fields against the hardcoded values.
- Display a welcome message with the username if successful and hide the form.
- Display an error message if not successful and redirect the user back to the form.
There are a few possible ways that you could go about trying to complete this activity. Here are three ideas for approaches you could adopt:
- Use a
singleTop
Activity and send an intent to route to the same Activity to validate the credentials. - Use a standard Activity to pass a username and password to another Activity and validate the credentials.
- Use
startActivityForResult
to carry out the validation in another Activity and then return the result.
The completed app, upon its first loading, should look as in Figure 2.23:
Note
The solution to this activity can be found at: http://packt.live/3sKj1cp.
The source code for all the exercises and the activity in this chapter is located at http://packt.live/3o12sp4.