Your web browser is out of date. Update your browser for more security, speed and the best experience on this site.

Update your browser
CapTech Home Page

Technical August 18, 2020

Android Activity Result Contracts

Madelyn Luansing Clinton Teegarden Dylan Doggett
Authors
Madelyn Luansing, Clinton Teegarden, Dylan Doggett

Recently Google introduced Android ActivityResultContracts which can be used to pass data between activities. These contracts are a huge improvement compared toonActivityResult() as they allow for the decoupling of unrelated data transfers and can be easily reused across multiple activities. ActivityResultContracts remove the need for conditional statements that were all too common a pattern with the legacy of onActivityResult() when deciding “what data is this / where is it from."

Before Activity Result Contracts

Previously, passing data from an exiting activity to the launching activity or handling permission request results would require creating Intents and overriding onActivityResult() and using some if / else or switch statements (or when statements if you prefer Kotlin). While this is easy enough to use, if your activity may need to handle the result of a variety of exiting intents / permission results. The onActivityResult() method can quickly become a long and complex mess that isn’t the easiest to read.

Suppose our MainActivity needs to handle requesting microphone, camera, and location permissions in addition to a string message received from another activity. That might look something like this.

Built-In Contracts

Google has already provided some commonly used ActivityResultContracts that can be directly passed into registerForActivityResult() along with a callback that will be executed when that activity result is found.

Take a Picture / Video

Easily capture images / videos from the Camera without having to deal with Media Intent. For photos use TakePicture() and for videos use TakeVideo().

Request Permission(s)

Ask the user to grant Runtime Permission(s). To request one permission use RequestPermission() which will return a singleBoolean indicating if the permission was granted.


To request multiple permissions simultaneously use RequestMultiplePermissions() which would return a map of Booleans indicating which permissions were granted.

And More!

Here’s the current list of all the built-in activity result contracts provided by Google.

Custom Contracts

Developers also have the ability to define and use their own custom Activity Result Contracts to handle Intents passed between activities. This allows developers to specify expected input and output types and easily reuse across multiple activities.

Getting Started

Activity Result APIs were introduced in AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 and they provide components for registering for a result, launching the result, and handling the result once it is dispatched by the system.

Add the following dependencies to the app’s build.gradle file. (At time of writing these were the latest versions of activity-ktx and fragment-ktx. Feel free to use any version later than this.)

Example Contract

Note: We will be using View Binding for code simplicity.

For this example, we want a simple MessageContract that will return a String from one activity to another.

Let’s start by defining a MessageContract which will create an Intent launching MessageActivity.

And a simple MessageActivity layout could contain an EditText and Button defining an onClick method called onSubmitMessage() . Here’s the MessageActivity layout XML from our code.

Let’s define how MessageActivity should handle the button click in onSubmitMessage() — by packaging the String inputted by the user in the EditText in an Intent, setting the Activity result to RESULT_OK , and finishing the activity withfinish().


When the MessageActivity is closed, the resulting String can be parsed from the Intent and returned in the MessageContract's parseResult() .


Note that in the above example we pass in Unit as the input and a String as the output. If you wanted to provide arguments to the next activity, you would do so in the input portion. We just use Unit when you want to specify the input as nothing.

In our receiving activity, we need to specify a variable for the ActivityResultLauncher returned by the registerForActivityResult() where we pass in our custom MessageContract() .


Here we will define a lambda that is to be executed when the contract parses the result. In this example, we are just displaying the String message in a Toast to the user. The result it is expected to be a String as was specified to be the output type by our contract — class MessageContract : ActivityResultContract<Unit, String>() .

To launch the messageLauncher in our receiving activity, we simply need to call messageLauncher.launch(). This will call the createIntent() method we overrode in our custom MessageContract and create and launch the MessageActivity Intent.


“Gotchas”

Be sure your contracts are available when the Activities process is created or recreated. It is possible that your process gets destroyed while the other Activity that you are awaiting your result from is working. If this happens and your result contract isn’t prepared by the time your Activity is recreated than the result will be missed. Do NOT make these local registrations that happen when a specific function is hit or button is pressed, which is also called out in Google’s documentation here.

Takeaways

ActivityResultContracts can be used to explicitly specify input and output types when handling the different data transfer cases between activities. You no longer need to override onActivityResult() and use a plethora of conditional statements to determine how to handle the exiting activity’s Intent. The contracts allow for the (1) decoupling of unrelated data transfers (2) easy reuse across multiple activities and (3) increased type-safety. They are definitely a clean, long-needed improvement for Android development.

Feel free to check out the sample application provided with this post.