By this stage of the book, it should be clear that Android applications comprise one or more activities, among other things. However, an area that has yet to be covered in extensive detail is the mechanism by which one activity can trigger the launch of another activity. As outlined briefly in the chapter entitled Understanding Android App Structure, this is achieved primarily using Intents.
Before working through some Android Studio-based example implementations of intents in the following chapters, this chapter aims to provide an overview of intents in the form of explicit intents and implicit intents, together with an introduction to intent filters.
An Overview of Intents
Intents (android.content.Intent) are the messaging system by which one activity can launch another activity. An activity can, for example, issue an intent to request the launch of another activity contained within the same application. Intents also go beyond this concept by allowing an activity to request the services of any other appropriately registered activity on the device for which permissions are configured. Consider, for example, an activity contained within an application that requires a web page to be loaded and displayed to the user. Rather than the application having to contain a second activity to perform this task, the code can send an intent to the Android runtime requesting the services of any activity that has registered the ability to display a web page. The runtime system will match the request to available activities on the device and either launch the activity that matches or, in the event of multiple matches, allow the user to decide which activity to use.
Intents also allow data transfer from the sending to the receiving activity. In the previously outlined scenario, for example, the sending activity would need to send the URL of the web page to be displayed to the second activity. Similarly, the receiving activity may be configured to return data to the sending activity when the required tasks are completed.
Though not covered until later chapters, it is also worth highlighting that, in addition to launching activities, intents are also used to launch and communicate with services and broadcast receivers. Intents are categorized as either explicit or implicit.
Explicit Intents
An explicit intent requests the launch of a specific activity by referencing the target activity’s component name (which is the class name). This approach is most common when launching an activity residing in the same application as the sending activity (since the class name is known to the developer).
An explicit intent is issued by creating an instance of the Intent class, passing through the activity context and the component name of the activity to be launched. A call is then made to the startActivity() method, passing the intent object as an argument. For example, the following code fragment issues an intent for the activity with the class name ActivityB to be launched:
val i = Intent(this, ActivityB::class.java)
startActivity(i)
Code language: Kotlin (kotlin)
Data may be transmitted to the receiving activity by adding it to the intent object before it is started via calls to the putExtra() method of the intent object. Data must be added in the form of key-value pairs. The following code extends the previous example to add String and integer values with the keys “myString” and “myInt” respectively, to the intent:
val i = Intent(this, ActivityB::class.java)
i.putExtra("myString", "This is a message for ActivityB")
i.putExtra("myInt", 100)
startActivity(i)
Code language: Kotlin (kotlin)
The target activity receives the data as part of a Bundle object which can be obtained via a call to getIntent(). getExtras(). The getIntent() method of the Activity class returns the intent that started the activity, while the getExtras() method (of the Intent class) returns a Bundle object containing the data. For example, to extract the data values passed to ActivityB:
val extras = intent.extras ?: return
val myString = extras.getString("myString")
int myInt = extras.getInt("MyInt")
Code language: Kotlin (kotlin)
When using intents to launch other activities within the same application, those activities must be listed in the application manifest file. The following AndroidManifest.xml contents are correctly configured for an application containing activities named ActivityA and ActivityB:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ebookfrenzy.intent1.intent1" >
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name="com.ebookfrenzy.intent1.intent1.ActivityA" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="ActivityB"
android:label="ActivityB" >
</activity>
</application>
</manifest>
Code language: HTML, XML (xml)
Returning Data from an Activity
As the example in the previous section stands, while data is transferred to ActivityB, there is no way for data to be returned to the first activity (which we will call ActivityA). This can, however, be achieved by launching ActivityB as a sub-activity of ActivityA. An activity is started as a sub-activity by creating an ActivityResultLauncher instance. An ActivityResultLauncher instance is created by a call to the registerForActivityResult() method and is passed a callback handler in the form of a lambda. This handler will be called and passed return data when the sub-activity returns. Once an ActivityResultLauncher instance has been created, it can be called with an intent parameter to launch the sub-activity. The code to create an ActivityResultLauncher instance typically reads as follows:
val startForResult = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) {
result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
val data = result.data
data?.let {
// Code to handle returned data
}
}
}
Code language: Kotlin (kotlin)
Once the launcher is ready, it can be called and passed the intent to be launched as follows:
val i = Intent(this, ActivityB::class.java)
.
.
startForResult(intent)
Code language: Kotlin (kotlin)
To return data to the parent activity, the sub-activity must implement the finish() method, the purpose of which is to create a new intent object containing the data to be returned and then call the setResult() method of the enclosing activity, passing through a result code and the intent containing the return data. The result code is typically RESULT_OK, or RESULT_CANCELED, but it may also be a custom value subject to the developer’s requirements. If a sub-activity crashes, the parent activity will receive a RESULT_CANCELED result code. The following code, for example, illustrates the code for a typical sub-activity finish() method:
override fun finish() {
val data = Intent()
data.putExtra("returnString1", "Message to parent activity")
setResult(RESULT_OK, data)
super.finish()
}
Code language: Kotlin (kotlin)
Implicit Intents
Unlike explicit intents, which reference the class name of the activity to be launched, implicit intents identify the activity to be launched by specifying the action to be performed and the type of data to be handled by the receiving activity. For example, an action type of ACTION_VIEW accompanied by the URL of a web page in the form of a URI object will instruct the Android system to search for and, subsequently, launch a web browser-capable activity. The following implicit intent will, when executed on an Android device, result in the designated web page appearing in a web browser activity:
val intent = Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.ebookfrenzy.com"))
startActivity(intent)
Code language: Kotlin (kotlin)
When an activity issues the above implicit intent, the Android system will search for activities on the device that have registered the ability to handle ACTION_VIEW requests on HTTP scheme data using a process referred to as intent resolution. Before the system launches an activity using an implicit intent, the user must either verify or enable that activity. If neither of these conditions has been met, the activity will not be launched by the intent. Before exploring these two options, we first need to talk about intent filters.
Using Intent Filters
Intent filters are the mechanism by which activities “advertise” supported actions and data handling capabilities to the Android intent resolution process. These declarations also include the settings required to perform the link verification process. The following AndroidManifest.xml file illustrates a configuration for an activity named WebActivity within an app named MyWebView with an appropriately configured intent filter:
<?xml version="1.0" encoding="utf-8"?>
.
.
<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.MyWebView">
<activity
android:name="WebActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https" />
<data android:host="www.ebookfrenzy.com"/>
</intent-filter>
</activity>
</application>
</manifest>
Code language: HTML, XML (xml)
This manifest file configures the WebActivity activity to be launched in response to an implicit intent from another activity when the intent contains the https://www.ebookfrenzy.com URL. The following code, for example, would launch the WebActivity activity (assuming that the MyWebView app has been verified or enabled by the user as a support link):
val intent = Intent(Intent.ACTION_VIEW,
Uri.parse("https://www.ebookfrenzy.com"))
startActivity(intent)
Code language: Kotlin (kotlin)
Automatic Link Verification
Using a web link to launch an activity on an Android device is considered a potential security hazard. To minimize this risk, the link used to launch an intent must either be automatically verified or manually added as a supported link on the device by the user. To enable automatic verification, the corresponding intent declaration in the target activity must set autoVerify to true as follows:
<intent-filter android:autoVerify="true">
.
.
</intent-filter>
Code language: HTML, XML (xml)
Next, the link URL must be associated with the website on which the app link is based. This is achieved by creating a Digital Assets Link file named assetlinks.json and installing it within the website’s .well-known folder.
A digital asset link file comprises a relation statement granting permission for a target app to be launched using the website’s link URLs and a target statement declaring the companion app package name and SHA-256 certificate fingerprint for that project. A typical asset link file might, for example, read as follows:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.ebookfrenzy.mywebview",
"sha256_cert_fingerprints":
["<your certificate fingerprint here>"]
}
}]
Code language: JSON / JSON with Comments (json)
Note that you can either create this file manually or generate it using the online tool available at the following URL:
https://developers.google.com/digital-asset-links/tools/generator
When working with Android, the namespace value is always set to “android_app”, while the package name corresponds to the app package to be launched by the intent. Finally, the certificate fingerprint is the hash code used to build the app. When you are testing an app, this will be the debug certificate contained within the debug. keystore file. On Windows systems, Android Studio stores this file at the following location:
\Users\<your user name>\.android\debug.keystore
Code language: plaintext (plaintext)
On macOS and Linux systems, the file can be found at:
$HOME/.android/debug.keystore
Code language: plaintext (plaintext)
Once you have located the file, the SHA 256 fingerprint can be obtained by running the following command in a terminal or command prompt window:
keytool -list -v -keystore <path to debug.keystore file here>
Code language: plaintext (plaintext)
When prompted for a password, enter “android” after which output will appear, including the SHA 256 fingerprint:
Certificate fingerprints:
SHA1: 11:E8:66:11:B6:94:3D:AA:7E:50:63:99:77:B8:6A:90:FF:B6:9C:6D
SHA256: 7F:EE:E3:C8:38:41:C3:EA:11:56:83:94:2A:4C:D2:EA:A0:69:F8:96:D1:17:77:02:46:EC:AD:6E:3C:64:A9:29
Code language: plaintext (plaintext)
When you are ready to build your app’s release version, you must ensure you add the release SHA 256 fingerprint to the asset file. Details on generating release keystore files are covered in the chapter entitled “Creating, Testing and Uploading an Android App Bundle”. Once you have a release keystore file, run the above keytool command to access the fingerprint.
Once you have placed the digital asset file in the correct location on the website, install the app on a device or emulator and wait 30 seconds for the link to be verified. To check the verification status, run the following at a command or terminal prompt:
adb shell pm get-app-links --user cur com.example.mywebview
Code language: plaintext (plaintext)
The resulting output should include confirmation that the link has been verified:
com.example.mywebview:
ID: 0e399bca-bf58-4cfc-8c7b-d1a6c3b065ec
Signatures: [7F:EE:E3:C8:38:41:C3:EA:11:56:83:94:2A:4C:D2:EA:A0:69:F8:96:D1:17:77:02:46:EC:AD:6E:3C:64:A9:29]
Domain verification state:
www.ebookfrenzy.com: verified
User 0:
Verification link handling allowed: true
Selection state:
Disabled:
www.ebookfrenzy.com
Code language: plaintext (plaintext)
You can also check the status from within the Settings app on the device or emulator using the following steps:
- Launch the Settings app.
- Select Apps from the main list.
- Locate and select your app from the list of installed apps.
- On the settings page for your app, choose the Open by Default option.
Choose the Open by Default option on your app’s settings page.
Once displayed, the page should indicate that a link has been verified, as shown in Figure 57-1:
To review which links have been verified, tap on the info button indicated by the arrow in the above figure to display the following panel:
The assetlinks.json file can contain multiple digital asset links, allowing a single website to be associated with more than one app. If you cannot use auto link verification, add code to your app to prompt the user to enable the link manually.
Manually Enabling Links
Where it is not possible to auto-verify links using the steps outlined above, the only option is to request that the user manually enable app links. This involves launching the Open by Default screen of the Settings app for the target app where the user can enable the link. Since the sudden appearance of the Open by Default screen may be confusing to the average user, it is recommended that an explanatory dialog be displayed before launching the Settings app.
To provide the user with the option to enable a link manually, the following code needs to be executed before attempting to launch the intent:
.
.
// Code here to display a dialog explaining that the link needs to be enabled
.
.
val intent = Intent(
Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:com.ebookfrenzy.mywebview"))
startActivity(intent)
Code language: Kotlin (kotlin)
The above example code will display the Open by Default settings screen for our target MyWebView app, where the user can click on the Add Link button:
Once clicked, a dialog will appear initialized with the link passed in the intent. This can be enabled by setting the checkbox as shown in Figure 57-4:
Checking Intent Availability
It is generally unwise to assume that an activity will be available for a particular intent, especially since the absence of a matching action typically results in the application crashing. Fortunately, it is possible to identify the availability of an activity for a specific intent before it is sent to the runtime system. The following method can be used to identify the availability of an activity for a specified intent action type:
fun isIntentAvailable(context: Context, action: String): Boolean {
val packageManager = context.packageManager
val intent = Intent(action)
val list = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY)
return list.size > 0
}
Code language: Kotlin (kotlin)
Summary
Intents are the messaging mechanism by which one Android activity can launch another. An explicit intent references a specific activity to be launched by referencing the receiving activity by class name. Explicit intents are typically, though not exclusively, used when launching activities within the same application. An implicit intent specifies the action to be performed and the type of data to be handled and lets the Android runtime find a matching activity to launch. Implicit intents are generally used when launching activities that reside in different applications.
When working with implicit intents, security restrictions require the user to automatically verify or manually enable the app containing the intent activity target before launching the intent. Automatic verification involves the placement of a Digital Assets Link file on the website corresponding to the link URL.
An activity can send data to the receiving activity by bundling data into the intent object as key-value pairs. Data can only be returned from an activity if it is started as a sub-activity of the sending activity.
Activities advertise capabilities to the Android intent resolution process by specifying intent filters in the application manifest file. Both sending and receiving activities must also request appropriate permissions to perform tasks such as accessing the device contact database or the internet.
Having covered the theory of intents, the next few chapters will work through creating some examples in Android Studio that put both explicit and implicit intents into action.