Android new Results API and how to use it to make your code cleaner

Guilherme Krzisch
The Startup
Published in
4 min readMay 28, 2020

--

Introduction

After years of having to receive activity results directly in Activity and Fragment classes, Google finally released a new API in order to decouple this functionality from these classes.

To demonstrate how this new API works, and how we could take advantage of its new capabilities to make our code cleaner and more testable, we are going to use a sample application. It consists of two Acitivities: ActivityA and ActivityB, where the first starts the second and waits for a success or a failure result.

ActivityA and ActivityB

Traditional approach

The official API since the first version of Android consisted of two points:

startActivityForResult(ActivityB.newIntent(this), ACTIVITY_A_REQUEST_CODE)
  • Receiving result (in ActivityA) from the other Activity (ActivityB) via onActivityResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { ... }

The second bullet point was what make it impossible for developers to decouple this logic from the Activity. The official explanation of why this could not be a callback (for example) on the startActivityForResult method, is that it would not work consistently after process death or configuration changes (e.g. when sending an Intent to receive a photo from the Camera, as this is a resource-intensive operation, especially on low-end devices, our Activity could be killed). A callback on the triggering method would not be saved under these conditions, but receiving the result on the onActivityResult works.

You can find the simple sample for this here.

New Results API approach

With the new Results API, we don’t have to deal with these two methods anymore: we can use the new prepareCall method to handle the result as a callback in the originating call. No more separate methods!

I will not dive into how it works, you can see for yourself on the official documentation. Here is the same sample as before, but using this new API. Compare this with the previous approach and check if you can see its benefits.

val startForResult = prepareCall(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> ... }

One caveat that you must remember is that all prepareCall methods should be called sequentially and in the same order every time the Activity is created.

Decouple New Results API using official documentation

One drawback with the previous approach, is that it stills is coupled with the Activity, but this is easily decoupled following the recommendations in the official docs: you can have another class implementing LifecycleObserver interface and use the corresponding ActivityResultRegistry.registerActivityResultCallback method that receives a Lifecycle owner.

private lateinit var startForResult : ActivityResultLauncher<Intent>override fun onCreate(owner: LifecycleOwner) {                                         startForResult = registry.registerActivityResultCallback(START_FOR_RESULT_KEY, owner, ActivityResultContracts.StartActivityForResult(), ActivityResultCallback { result -> ... }      
}

Following the same sample, you can find the example here.

Remove explicit dependency on LifecycleOwner

Note that, while in the previous approach we are no longer starting/receiving results directly in the Activity, our “separate class” (we called it Controller here, but you can call it whatever you want in your favorite presentation layer architecture — MVC/MVP/MVVM/MVI/…) still has to implement LifeycleObserver, which could be considered to be a tight coupling to the Android framework classes.

How can we remove this dependency? There is a hint in the official docs: when there is no LifecycleOwner, use ActivityResultRegistry.registerActivityResultCallback method that does not receives a Lifecycle owner, and remember to dispose() when no longer using it.

In order to do this, we can use, for example, LiveData: by subclassing it, we can call registerActivityResultCallback in the onActive method, and dispose the results in the onInactive method. There is one important caveat though: as we must always register when creating the Activity, we cannot use the usual observe method of LiveData, which automatically remove the observer when the view is not active. Instead, we must use the observeForever and manually remove the observer in the onDestroy method.

Here is the final sample code.

While in this article, we choose to use LiveData, which internally stills uses LifecycleOwner, keep in mind that a similar approach would also work with other libraries, as, for example, RxJava (or manually if you prefer). And perhaps, using other approaches would be a better idea in this case, as currently we cannot use the usual observe method of LiveData, which can be a source of bugs in real applications.

Conclusion

As an exercise for the reader, the next steps for this POC would be:

  • Add an example with Fragments. The resulting sample should be similar to what was shown in the article.
  • Add UI and unit tests: UI tests should be straightforward for all the solutions, but for unit tests the last approach has a clear advantage
  • Add an example with RxJava for the final approach
  • Use a custom contract instead of using the equivalent of the current API: in this article we have not touched upon another advantage of the new API, which is the ability of defining custom contracts with expected input and output, providing type-safety for our app

You can find the complete sample repository here.

Further reading

--

--

Guilherme Krzisch
The Startup

Software Engineer | Mobile Developer | Android Developer