The right way to get a result (Part I): Activity Result API

Leonid Belyakov
e-Legion
Published in
5 min readMar 17, 2021

Every Android developer has faced the need to transfer data from one Activity to another. This simple task often forces us to write not the most elegant code.

In 2020, Google finally introduced a solution to the old problem — the Activity Result API, a powerful tool for exchanging data between activities and requesting runtime permissions.

In this article, we’ll look at how to use the new API and highlight its benefits.

What’s wrong with onActivityResult()?

Robert Martin in his book “Clean Code” points out the importance of reusing the code or the Don’t repeat yourself principle (DRY) and encourages designing unambiguous functions that perform only a single operation.

The problem with onActivityResult() is that it becomes almost impossible when it is used following these recommendations. For example, let’s look at the simple screen that requests access to the camera, takes a photo, and opens a second screen — SecondActivity. Let’s suppose that we pass a string to SecondActivity and get an integer value back.

It’s obvious that the onActivityResult() method violates the single-responsibility principle because it is responsible for processing the photo result and for receiving data of the second Activity. Besides, this method looks confusing enough, even though we’ve considered a simple example with some details omitted.

Additionally, if another similar functionality screen appears in the app, we won’t be able to reuse our code and will have to duplicate it.

Using Activity Result API

The new API is available starting with AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02, so let’s add the current versions of the corresponding dependencies to build.gradle:

Applying an Activity Result consists of three steps:

Step 1. Creating a contract

Contract is a class that implements the ActivityResultContract<I,O> interface. Where I defines the type of input data necessary to start the Activity, and O defines the callback result type.

For typical tasks, you can use out-of-the-box implementations: PickContact, TakePicture, RequestPermission, and others. You will find a complete list here.

When creating a contract, we should implement two of its methods:

  • createIntent() — accepts input data and creates an intent, which will be later launched by calling launch()
  • parseResult() — is responsible for returning the result, handling resultCode, and parsing the data.

Another method, getSynchronousResult(), can be overridden if necessary. It allows you to return the result immediately, without starting the Activity, for example, if the received input data is invalid. If this behavior is not required, the method returns null by default.

Below is an example of a contract that accepts a string and runs SecondActivity expecting an integer value from it:

Step 2. Registering a contract

The next step is to register the contract in the activity or fragment by calling registerForActivityResult(). You need to pass ActivityResultContract and ActivityResultCallback as parameters. The callback will be invoked when the result is received.

Registering a contract won’t start a new Activity, but only returns a special ActivityResultLauncher object that is necessary for the following step.

Step 3. Launching a contract

To start the Activity, we only need to call launch() on the ActivityResultLauncher object that we have obtained in the previous step.

Important!

Let’s highlight a few non-obvious points that must be taken into account:

  • Contracts can be registered at any point of the activity or fragment lifecycle, but they cannot be launched before going to the CREATED state. A common approach is to register contracts as class fields.
  • It is not recommended to call registerForActivityResult() inside the if and when statements. The reason is that while expecting the result, the application process can be destroyed by the system (for example, when launching a heavy camera app). And if we don’t re-register the contract when restoring the process, the result will be lost.
  • If we start an implicit Intent and the operating system is not able to find a suitable Activity, an exception ActivityNotFoundException: “No Activity found to handle Intent” will be thrown. To avoid this situation, you need to check resolveActivity() with PackageManager before calling launch() or in getSynchronousResult() method.

Runtime permissions

Another handy Activity Result API usage is runtime permissions request. Instead of calling checkSelfPermission(), requestPermissions(), and onRequestPermissionsResult(),a concise and convenient solution is available now — RequestPermission and RequestMultiplePermissions contracts.

The first serves to request single permission, and the second to request several permissions at once. In the callback, RequestPermission returns true if access is granted, and false otherwise. RequestMultiplePermissions will return Map<String, Boolean>, where the key is the name of the permission requested and the value is the result of the request.

In real life, requesting permissions looks a bit more complicated. In Google’s guidelines, we’ll see the following:

Google guidelines of requesting permissions

Developers often forget about these nuances when dealing with runtime permissions:

  • If the user has previously rejected our request, it is advisable to further explain why the application needs this permission (see 5a).
  • When rejecting a permission request (see 8b), we should limit the functionality of the application and consider the case when the user has checked the “Don’t ask again” option.

This is where the showRequestPermissionRationale() method comes to our rescue. If the method returns true before requesting permission, we should tell the user how the application will use permission. If permission is not granted and shouldShowRequestPermissionRationale() returns false, it means that the “Don’t ask again” option was selected and we should ask the user to go into settings and grant permission manually.

With this in mind, let’s implement the device’s camera access request:

Conclusion

Now let’s bring our knowledge into practice and use it to rewrite the first screen example. As a result, we’ll get a quite neat, easy-to-read, and scalable code:

Today we’ve seen the disadvantages of data exchange via onActivityResult(), learned how to connect Activity Result API to your project and how to use it in practice.

Also, note that the new API is totally stable, while the commonly used onRequestPermissionsResult(), onActivityResult() and startActivityForResult() are now Deprecated.

In my Github repository, you will find a demo application with a variety of examples of using the Activity Result API including working with runtime permissions.

--

--