Activity Result Dispatcher


NOTE: This is now a part of AndroidX via the Activity Results API, which is under alpha at this time. This is another solution to achieve mostly the same result, but allows additional flexibility and a similar type-based implementation.


As some of you may know, the Android Activity has the ability to "request" data from another Activity; taking a picture requires launching the camera and fielding the URI result that is returned, selecting a file from the File Manager requires requesting and fielding it in a similar fashion. This is normally done via a round trip flow using startActivityForResult to request the data (e.g for a result) and to handle it in onActivityResult.

The downsides of this are that is tedious:

  1. Monotonous callbacks and request flows
  2. Checking request code equality
  3. Checking resultCode is Acitvity.RESULT_OK
  4. Extracting your data out of the returned Intent
  5. Handling it or fielding the error that is a possibility

This also becomes increasingly difficult when ViewPager and inner fragments come into play, forwarding the result to the sender, as you have to pass it along.

A (possible) solution to the monotony

signInButton.setOnClickListener {
		val signIn = SignInWithInstagram(context)
		signIn.start(object : SignInWithSocialAccountDispatcher {
		        override fun onSuccess(result: String?) {
		            // handle auth token from successful sign in
		        override fun onFailure(error: Throwable) = Unit
		        override fun onCancel() = Unit

So what exactly is happening here? One scenario for an activity result feedback-cycle is authentication via a dedicated login activity (or in this case a webview for Instagram authentication). An instance of SignInWithInstagram, a subclass of our activity result dispatcher, is created with a Context and optional other arguments. start() triggers the startActivityForResult internally tracking the REQUEST_CODE automatically, and sending the result back to us - INLINE.

This grants us the awesome ability to handle it in a simple interface approach with explicit success, failure, and cancel contracts - that are strongly typed based on the interface used for the activity result dispatcher.

The ActivityResultContainer and dispatcher interface

To strongly type this we are taking advantage of generics and an explicit result interface.

interface ActivityResultDispatcher<S> {
    fun onSuccess(result: S? = null)
    fun onFailure(error: Throwable)
    fun onCancel()

This is designed so onSuccess returns a nullable S, removing the boilerplate post processing, onFailure emits any exception that your activity might hit (expected or unexpected), and onCancel will signal an explicit Activity.RESULT_CANCEL result sent from the Activity.

abstract class ActivityResultContainer<D: ActivityResultDispatcher<*>> : CoroutineScope by CoroutineScope(Dispatchers.Main) {
    fun start() {
    abstract fun start(cb: D? = null)
    fun sendIntent(
            dispatcher: ReactiveActivityResult,
            intent: Intent,
            callback: D?
    ) {
        launch {
            dispatcher.start(intent).collect { handleResult(it, callback) }
    internal abstract fun handleResult(result: ActivityResult, callback: D?)

The strongly typed interface, coupled with a typed abstract class allows seamless extensibility from our subclasses -


create Intent → start() → handleResult → send back to requester (in the data format they want).

The nuts and bolts

This is all pulled off under the hood using what we will refer to as ReactiveActivityResult. A Fragment (now referred to as ReactiveActivityResultFragment ) is created and added to the FragmentManager based on the context provided:

  • If an Activity context is provided, the SupportFragmentManager will be used
  • If a Fragment context is provided, the ChildFragmentManager will be used.

The fragment will then be used facilitate the startActivityForResult song-and-dance that you normally would have to do, all behind the scenes, using a BroadcastChannel and Flowable.

The handoff in ReactiveActivityResultFragment:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    launch {
        channel.send(Tuple2(requestCode, ActivityResult(resultCode, data)))

fun start(intent: Intent): Flow<ActivityResult> {
    val requestCode = RequestCodeGenerator.generate()
    startActivityForResult(intent, requestCode)

    return flow {
        coroutineScope {
            channel.consumeEach {
                if (it._1 == requestCode) {

The results are handled like we normally would, request code is matched to the one created via our request code generator, and forwarded to our subclass for consumption via our Flowable<ActivityResult>.


Activity Results are able to be boiled down to a strict object based entity that make this syntactically easier to handle in our subclasses, as well as follow logically, complete with extension methods to remove the primitive equality checks.

data class ActivityResult(val resultCode: Int, val data: Intent?) : Parcelable
fun ActivityResult.isOk() = resultCode == Activity.RESULT_OK
fun ActivityResult.isCanceled() = resultCode == Activity.RESULT_CANCELED
fun ActivityResult.isFirstUser() = resultCode == Activity.RESULT_FIRST_USER

Our channel conforms the requestCode: Int, resultCode: Int, data: Intent? contract of onActivityResult to our ActivityResult for the Flowable to forward along if the request code is a match.

ActivityResult(resultCode, data)

Handling the ActivityResult in our Dispatcher

For our example we are using a dedicated login activity for an IG authentication process, with our interface for our dispatcher resembling something like this:

interface SignInWithSocialAccountDispatcher: ActivityResultDispatcher<String>

where the result (a String) is the authenticate success URI from the backend, containing our access token.

Our subclass then becomes this:

class SignInWithInstagram @JvmOverloads constructor(
        private val ctx: Context?
) : ActivityResultContainer<SignInWithSocialAccountDispatcher>() {

    override fun start(cb: SignInWithSocialAccountDispatcher?) {
        ifNotNull(ctx, { "context instance is null." }) {
            // create reactive dispatcher
            val reactiveDispatcher = ReactiveActivityResult(it)

            // create the Intent
		        val requestIntent = Intent()
		        // send it
                    dispatcher = reactiveDispatcher,
                    intent = requestIntent,
                    callback = cb

    override fun handleResult(result: ActivityResult, callback: SignInWithSocialAccountDispatcher?) {
        when {
            result.isOk() -> {
                val uriString ="uri")
                if (uriString == null) {
                    callback?.onFailure(Throwable("uri wasn't returned properly. Try again."))
                } else {
                    val uri = uriString.toHttpUrlOrNull()
                    val tokens = uri?.queryParameterValues("access_token")
            result.isCanceled() -> callback?.onCancel()

As you can see, this forwards the pertinent data for the Activity is actually after (the auth token), abstracting ALL of the boilerplate request/result and data handling away, and does so inline in a normal callback fashion.

signIn.start(object : SignInWithSocialAccountDispatcher {
        override fun onSuccess(result: String?) {
            // handle auth token from successful sign in

        override fun onFailure(error: Throwable) = Unit
        override fun onCancel() = Unit

Last thoughts

This abstraction and ability to handle Activity results inline at the point of request has been extremely helpful at Planoly. This solution, one which I was faced with solving prior to the Activity Result API being made available as a part of Jetpack, is just one of many possibilities. As I refine this experience, such as making it more lifecycle conscientious via lifecycleObserver, I will continue to update this blog post.

Let me know your thoughts on Social Media, and as always thanks for reading!