Deep Dive into Activity Results API — No More onActivityResult()
ActivityResultContracts by Examples
Since Android came into existence in 2007, Activity
has been one of its core components. One of the most common tasks in apps is transferring data between two Activities. Until now, Intents
and onActivityResult
were the only choice.
Through the combination of these two parts, developers are able to transfer data from one Activity
to another and get data back easily.
Let’s elaborate through an example: your app wants to capture an image and display it to the user. You either write your own custom Camera or delegate the task of fetching an image to Android through Intents
. In this second scenario, Android system will open the user-preferred Camera app, capture the image and deliver the requested data to your activity by calling onActivityResult()
method.
I am working on the video tutorial of this article and will upload it on my YouTube channel soon. Please subscribe to watch that and more Android tutorials like this.
Subscribe to My YouTube Channel
Traditional Way — The onActivityResult() Method
Whether its an image Bitmap
from Camera app or its a Image from the gallery, or maybe its some custom result from your some other Activity of app, Android system will call onActivityResult()
method in the original requesting Activity
or Fragment
class.
If you one case of capturing image or multiple cases like picking images from gallery, requesting gazillion permissions, and handling your own custom data with app’s other screens, all the results of these actions are handled in only one method onActivityResult()
. And this method will look something like this in your code.
I have a feeling that you don’t like this kind of code to read / write at all. Neither do I. Because this kind of code brings lots of problems and unwanted bugs such as:
Tight Coupling: There’s no other place where you can put or abstract this code and business logic. Like if you want to separate each case of Image Capture or Pick Image etc. You can’t do that. You can delegate later, but starting point is this. And this will create a bad cluster like above code with nested if-else blocks.
Type-Safety: You may get wrong type of data by minor mistakes. You can get your Integer value as String and later spend hours on debugging why app’s not working the way its supposed to. This is because you are relying on String keys to put/read from Intent data. You will have to make sure that you are using right type for the right key.
Unwanted NullPointerException: Who is not annoyed of
NullPointerException
? If you get a typo in writing key of to either put or retrieve data in theIntent
, you will get theNullPointerException
like a bombing crash in your app. This can waste a lot of time as you might debug on why your data is null. How are you supposed to think that you can mistakenly miss some letter in your key at that time?
So, Google brings us a solution which is cleaner in code readability with not much if-else blocks, less coupling and separate places for each case, type safety to make sure the right data gets to right method, and absolutely no NullPointerException
because of typos.
Introducing Activity Results API — The New Way
I’m not sure what this will be called — Activity Results API or Activity Result Contracts or both. But surely it sounds nice.
Starting with Activity 1.2.0-alpha02
and Fragment 1.3.0-alpha02
, you now have a nice abstraction which allows you to handle onActivityResult()
method in a very clean and reusable structure.
Let’s see how this new API is used then.
Adding Dependencies
First, you have to add the following dependencies in your app’s build.gradle
file.
Please note that at the time of writing this article, latest version is 1.2.0-alpha04
for activity-ktx
and 1.3.0-alpha04
for fragment-ktx
. You can check the latest version from the Google Maven link. These APIs are in the alpha stage yet, so API is not final yet. These can be changed at any time. The article works with alpha04
version but may or may not work with earlier or later versions.
The Process for Activity Results API
The process to use this new API looks like this.
1. Create Contract
First, you have to either define your contract or use any existing one. A contract is the implementation of ActivityResultContract
interface. Taken from the documentation, this is a contract specifying that an activity can be called with an input of type I and produce an output of type O.
Here’s a an example of SimpleContract
which takes an Integer as input and returns some String data from the child Activity.
The createIntent()
method is used to create the valid Intent
which will be passed in startActivityForResult()
method once this contract is called. And the parseResult()
method will behave as a proxy for onActivityResult()
method and is parsing the result Intent
and extracting the data from it.
You can see that we don’t need to put bunch of if-else blocks in onActivityResult()
method anymore. Because each case will be handled like this in separate implementation and separate parseResult()
method. This is the beauty and simplicity of the Activity Results API.
2. Register Contract
The next step is to register this SimpleContract
with the Activity Results API. This is done through calling registerForActivityResult()
method.
Please note that in previous versions
alpha02
andalpha03
, this method was called asprepareCall()
. But inalpha04
, this was renamed toregisterForActivityResult()
.
You can see how easy it to register your contract. And with the simplicity of beloved Kotlin, you will get the result in a nice lambda method. This will be called after the parseResult()
method from the contract and will give you the result (in our case a nullable String). You can check for the NULL value to see if user cancelled the action or the result was RESULT_OK
from the child activity.
3. Call Contract
Finally, all you have to do is make the call to this new registration variable which is simpleContractRegistration
in the above snippet. Go ahead and call it just like any ordinary method. And all the input you defined in the contract will be passed as type-safe parameters in your method call.
And voila. No more onActivityResult()
now. You can easily use this with your own custom Activity
or Fragment
classes and it will be very clean, organized, and reusable code with type-safe parameters.
Pre-Built Contracts
Now that you have seen how simple it is to use the Activity Result API and create your own contracts. You will be super glad to know that Google has provided some very useful pre-built contracts. These can be accessed statically from the ActivityResultContracts
class. Let’s explore some examples below.
Capturing Images — ActivityResultContracts.TakePicture()
You can easily capture the images from Camera without any hassle of Media Intent.
Pick Images — ActivityResultContracts.GetContent
This allows you to pick not only images but you can also select other files from your device. You have to provide mimeType of the files you need in app.
Requesting Permissions — ActivityResultContracts.RequestPermission
Now this is my most favorite one. I remember that I had to write like 100 lines of code to ask for multiple permissions to handle a very complex flow. I had written about it details in this article when there were no Activity Results API yet.
Multiple Runtime Permissions in Android Without Any Third-Party Libraries
The Activity Results API provides two methods — RequestPermission
and RequestMultiplePermissions
. These two does exactly what their names are. Here’s a quick example code.
Demo Code
I have created a simple demo project with some example contracts as discussed in this article on the following Github repository. You can explore more and play with it.
wajahatkarim3/ActivityResultsDemo
Conclusion
Wrapping it up now, in this article we discussed about some problems in the traditional onActivityResult()
way. And then we got to learn about new Activity Results API. Then we created an example contract to see how different parts work together. And finally we saw some pre-built contracts like image capturing, picking images, permission handling etc.
At the end, please don’t forget to Subscribe to my newsletter to get more tutorials and tips on Android development directly in your inbox.
Thanks to Roberto Orgiu for reviewing and providing feedback on this post 🙌