DEV Community

Cover image for Consuming Activity Results using coroutines: Part 2
Hicham Boushaba
Hicham Boushaba

Posted on

Consuming Activity Results using coroutines: Part 2

This is the second post of a 2 parts article on how we can convert and consume Activity Results using coroutines.
In the first part, we were able to create a component that allowed us to convert the Activity Result's contract RequestMultiplePermissions to a Coroutine, while still able to handle Activity Recreation and process-death scenarios.

Generalize our code for all Activity Results

When reviewing the code we had for our PermissionManager class, the only specific parts related to permissions were:

  1. The instantiation of RequestMultiplePermissions.
  2. The mapping of the result to our defined sealed class.
  3. Saving the permissions' list to the saved state, and using it as the flag for deciding if there is any pending operation.

So for designing our component that can be used to consume all Activity Results, we just need to generalize or remove the above points.
The first two points are straight forward, as we are aiming for a generic component here, we'll just remove them, so we'll pass the instance of our ActivityResultContract to our function, and we'll make it return the same type as defined by the contract. which means having a signature for our main function as this:

suspend fun <I, O, C : ActivityResultContract<I, O>> requestResult(
        contract: C,
        input: I
    ): O?
Enter fullscreen mode Exit fullscreen mode

For the third point, we have many ways for handling it:

  1. Using a simple boolean to tell us if there is any pending operation. This would work for most cases, but it has one downside, if a process-death occurs, and in the ViewModel's side we don't handle it, our component will still think that there is a pending operation, which means if we request a different type of results, it'll get stuck.
  2. By saving the contract's class name, and comparing against it, this is a simple way for doing it, and would work for the majority of cases, although it may still break on a very specific case: if the ViewModel doesn't handle process-death, and we request another result using the same type of Contract, but using a different input, we'll probably return the wrong result.
  3. By saving both the contract's class name and the input, and while this the ultimate solution, the comparison of the input against a saved value won't be straight forward, since it's a generic type, and we don't know if it has a valid equals implementation or not.

I opted for the option two, since the failure scenario is a very far corner case, and when using the SavedStateHandle correctly to save state, it won't occur at all, let me know what you think about this choice in the comments.

With all of this, we can have our component now:

Meet the library SuspendActivityResult

After starting the article, I decided to publish this component as a library for anyone interested, it's available in the same repo.
The Activity Result can be requested either using the low level API:

val uri = ActivityResultManager.getInstance().requestResult(
    contract = GetContent(),
    input = "image/*"
Enter fullscreen mode Exit fullscreen mode

or using the extensions for the built-in ActivityResultContracts

val uri = ActivityResultManager.getInstance().getContent("image/*")
Enter fullscreen mode Exit fullscreen mode

The example app has samples for using it for permissions and other activity results, and it has a sample for using it for a custom ActivityResultContract.


If you have any remarks or questions regarding the code or the API, please drop them in the comments section, I hope the library can be useful to some people.

Top comments (0)