Consuming Activity Results using coroutines: Part 1
Tags: android, kotlin, coroutines
Published: 2021-11-16 19:46:57

Handling results from other Activies was always a tedious task: you need to override callbacks in the Activity/Fragment, and you need to manage the different request codes, and when you get a result, you need to parse it and this was never easy*.
> \* will the returned Uri be in `intent.data` or passed in the `extras` (looking at you `RingtoneManager.EXTRA_RINGTONE_PICKED_URI`)? what's the key of the returned data inside the `extras` and what's its type?...

To solve the above issues, the Android team introduced the androidx's [Activity Result API](https://developer.android.com/training/basics/intents/result), it offers a nicer API, and it's easily extendable by writing custom `Contracts`, but it still requires doing most of the work on the UI layer, which means lots of going back and forth between the View and the ViewModel for simple tasks.

This post is the first of two posts where we'll explore how we can optimize the API, by converting it to suspendable functions, and allowing it to be consumed by the ViewModel instead of the UI layer.
On this first part, we'll use permissions as an example, for this We'll start by showing an example on how the Activity Result API can be used to handle them, and then we'll try to write the new suspendable API together, and see how it can improve the readability of our code.
The code of the example can be found in this [repo](https://github.com/hichamboushaba/SuspendActivityResult).

> Part 2 is available [here](https://dev.to/hichamboushaba/consuming-activity-results-using-coroutines-part-2-54mf).

## Activity Result API in play
Using the Activity Result API is quite simple, you just need to register an [ActivityResultContract](https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContract) with your Activity/Fragment, while offering a callback that will be called when the result is delivered:
{% gist https://gist.github.com/hichamboushaba/008915a02929205b72f284d5418aecd6 %}
The registration will return an [ActivityResultLauncher](https://developer.android.com/reference/kotlin/androidx/activity/result/ActivityResultLauncher?hl=en) which you will use whenever you need to request the result
{% gist https://gist.github.com/hichamboushaba/ec57ac1833e0d6bed4f3cf90a411329d %}
But while it's simple now, it becomes a bit complicated when we try to hook everything with the ViewModel, as it would require multiple two-way communication steps for something simple as the permission:
{% gist https://gist.github.com/hichamboushaba/b529828509e44d867fdb6d4d1aa65952 %}
{% gist https://gist.github.com/hichamboushaba/9d6a4aed516fa2ebff8fdf289c9fe6fd %}
This is all while omitting some necessary parts, especially dealing with configuration changes and process-death.
For a full example, check the following sample [fragment](https://github.com/hichamboushaba/SuspendActivityResult/tree/88ea62fdf29fb3a3cf46f33cffb87f0742167d96/app/src/main/java/com/hicham/activityresult/permission/regular). 

I feel like with this code, the View is doing so much, and we can't easily reuse this code if we need to request the same permission in multiple parts of the app.

## Converting the Activity Result to suspendable functions: Permissions as an example
On this part, we'll try to move the logic of Activity Result API to another layer, to allow it to be consumed by the ViewModel.
Our goal is to have something similar to this by the end:
{% gist https://gist.github.com/hichamboushaba/28c511857c41ccbddbc516303255d732 %}

Let's get started, since the Activity Result API needs a reference to the Activity or the Fragment, the first thing we'll need, is a reference to the current Activity, we can use the function `registerActivityLifecycleCallbacks` to listen to Activity lifecycle changes, and keep a track of the latest one, a short example on how we can achieve this:
{% gist https://gist.github.com/hichamboushaba/0234bc6f089df5fdbd3228dc9c530ebd %}
This class needs to be a singleton, and initiated in the Application's onCreate.
With this in hand, we can now "naively" write something like this easily:
{% gist https://gist.github.com/hichamboushaba/a29b6cbe9fb7cc3e43d2e6160d7b175d %}
Before testing our code, let's take a moment to check what it does:
- We use a `suspendCancellableCoroutine` to convert the Activity result to a suspendable function.
- We can't register our `Contract` directly using the function `registerForActivityResult`, as it requires to be called before `onCreate`, instead we use the `activityResultRegistry` directly, and here we just need to handle the unregistering ourself, so we use the `finally` block to make sure we are unregistering in either a cancellation or completion.

Now, let's test the code: 
{% gist https://gist.github.com/hichamboushaba/5413ac2a1bda575acf5c6945ffd73083 %} 
When testing this, we'll see that it works well, until you rotate your phone, or a process-death occurs, when we'll leak the activity, and also we won't be able to recover to continue handling the result.
#### Fix the issue for screen rotation
This happens because we are capturing the current Activity in a variable, this variable stays available for the whole function's lifetime, which is longer than the Activity's lifecycle. To solve this, we need a way to listen to the Activity changes, to be able to cancel the previous `block` and unregister the `Contract`, then re-register with the new Activity, so we need to change our ActivityProvider's implementation to offer a Flow instead of a single variable, which can bee easily done using `MutableStateFlow`, for full code check [this](https://github.com/hichamboushaba/SuspendActivityResult/blob/master/app/src/main/java/com/hicham/activityresult/ActivityProvider.kt).
With those changes, we can now use Flow's `mapLatest` operator to handle registering our `Contract`, and unregistering it when the block is cancelled, since it would occur everytime the `Activity` wach changed, which would give something like this:
{% gist https://gist.github.com/hichamboushaba/39e64ecaac1fb223a15d14ca807ee7ea %}
Let's examine what the code does:
- We capture the value of the key outside of the Flow, to make sure we keep using the same value for other Activities.
- We have a variable `isLaunched` to let us know that the `ActivityResultLauncher` has been already launched, to avoid re-submitting the Intent or permission request.
- the suspendable funtion passed to `mapLatest` will be cancelled each time the upstream Flow emits a new value, so it does exactly what we need, it suspends waiting for the Activity Result, but if the `Activity` was changed before, it cancels the current block, and starts a new one with the new Activity.

When testing the new code, it works pretty well on screen rotations, we can continue the Flow from where we left, great.
Except that it has an issue, if a process-death occurs, or the Activity was destroyed to release memory (or when "Don't keep activities" option is enabled), we can't recover even if our ViewModel uses `SavedStateHandle` to restore its state, since the `viewModelScope` will be cancelled, and our function will lose its state, which takes us to the last part of this article, how we can use the Activity's `SavedStateRegistry` to save our state, and make sure that even when we make a second call to `requestPermissions` it will continue from where it left.
#### Saving the state
`ComponentActivity` from androidx has a component called `SavedStateRegistry` that allows plugging in custom components to save additional data and retrieve it later, and this is exactly what we need here, since we can't use the Activity's callbacks, we'll use it to keep track of the last key we used to register our `Contract`, to be able to intercept the Activity Result even after process-death:
{% gist https://gist.github.com/hichamboushaba/440939bf85300b1fe634580d19bf018d %}
The code seems complicated, but it differs from the previous step in only the fact that it tries to restore the last used key from the `SavedStateRegistry`, and also it registers our `SavedStateProvider` to save the state when needed.
If we test the code now, using a `ViewModel` that uses `SavedStateHandle` for saving/restoring its state correctly, we'll see that we continue to receive the Activity Result correctly even after a process-death.
#### What we can do with this component now
Being able to use a suspendable function like this here, makes it way easier to extract our logic into a reusable components, for example if your app requests the permission from multiple areas, and uses the same logic for showing the rationale or handling a pemanent denial, you'll be able to create a component or a usecase that handles this for you, without having to repeat it on each Fragment/Activity.
For example a class like this:
{% gist https://gist.github.com/hichamboushaba/91169767c24105990e65c14e2acec2f3 %}
This component is self contained, and allows to handle the whole permission request Flow, and can be reused on every screen easily.
A full example can be found [here](https://github.com/hichamboushaba/SuspendActivityResult/blob/master/app/src/main/java/com/hicham/activityresult/permission/LocationPermissionController.kt).

## Conclusion
To sum up, the callback aspect of the new Result Activity API allows it to be converted to suspendable functions relatively easy, instead of needing an intermediate Fragment or Activity for handling the callbacks (as many libraries do), and this conversion made it easier to extract pieces of the logic to their own component. But the code is not tested against all scenarios, especially when the app has multiple activities, so we'll try to check this in the next part, and we'll try to generalize what we did with permissions so far for all the Activity Result Contracts.
Please let me know in the comments if you have any remarks about this solution, thanks for your time.