Activity Results Dispatcher

Activity Results 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.

Foreword

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:

  • Monotonous callbacks and request flows
  • Checking request code equality
  • Checking resultCode is Activity.RESULT_OK
  • Extracting your data out of the returned Intent
  • 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

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.

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.

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:

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>.

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.

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:

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

Our subclass then becomes this:

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.

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!