Jetpack Example with Service Call Canceling

I’ve been working hard to update my Android skills by using Dagger, Retrofit, Kotlin and architecture components. I’ve found that a great way to learn about this new world is to build upon two Google sample projects:

These demonstrate different approaches to Jetpack development. I’ve taken what I think are the best ideas from each and combined them to form a new sample project that others may find useful.

In addition, I implemented a design for canceling the underlying web service connection from Retrofit REST calls. I also introduced a means of adding intentional delays into these requests while testing.

Comparing the Samples

Let’s get into the weeds for a moment.

The Github Sample adapts Retrofit Calls into LiveData. The repositories then implement abstract methods from NetworkBoundResource that, in turn, allow the ViewModel to observe the result. In contrast, the I/O conference app relies heavily on Firebase and adds a domain layer with Use Cases to handle “discrete pieces of business logic off the UI thread”.

Dagger setup in Github Browser is contained in the di package while the larger I/O app uses scope annotations and individual Module classes packaged alongside corresponding Fragments and ViewModels.

The I/O app uses helpers, DaggerApplication, DaggerAppCompatActivity and DaggerFragment, and uses the generated binding classes to inflate layouts. Github Browser uses AutoClearedValue to ensure memory is freed when Fragments move to the back stack. Both projects use EventObserver, but Github uses a Resource class instead of I/O app’s similar Result class.

My Favorite Parts

So with that, I mixed-and-matched. I threw out the slick Retrofit Call adapter and NetworkBoundResource in favor of Use Cases. I grabbed the multiple Module files and Dagger helpers from here and the AutoClearedValues and Results from there. Then I rearranged them into a simple example with one app module and 5 top-level packages: data, di, domain, ui and util.

And, Request Cancellation!

The ability to cancel web service requests is essential. Take the example I described in “Navigation Component with a Shapeshifting Fragment“. When a user taps Login, we can disable the button and call a remote service. If they click back before the service responds, we should cancel the request at the network layer. The screen shape-shifts back and the button is re-enabled.

Canceling can be tricky. It must ripple from the Fragment to its ViewModel to the Use Case (ExecutorService) and the Repository (Retrofit2). It should happen on-demand or when exiting the Fragment.

This Demo

This app calls a service I wrote that purposefully delays its response by 3 seconds; this allows for easier testing of cancellation. The service is currently public and returns an increasing userId with each non-canceled request.

Change SignupUseCase to use getSignupDemoFast() for no delay.

Change the build variant to debug_with_delays and there will be a 3 second delay on the client-side before making the service call.

Ingredients:

MainActivity and Navigation

The layout for MainActivity contains NavHostFragment and sets navGraph to navigation_main.xml. WelcomeFragment is the starting destination. We set up the top app bar, hook up NavController and facilitate the up-arrow by overriding onSupportNavigateUp().

WelcomeFragment

Screenshot

This Fragment is pretty simple, but serves a purpose here. Click events for its “Next” button are handled by signupClicked() in its view model. The view model triggers navigation by using an observed Event—read more about the significance of the Event class. The Fragment reacts by placing itself on the back stack and then moving to SignupFragment. We’ll use this later to ensure requests made in SignupFragment are canceled when returning to this screen.

SignupViewModel

Screenshot

Aside from the navigation piece, SignupFragment is identical to WelcomeFragment. The fun begins in SignupViewModel. When Sign Up is clicked, doSignup() changes the LiveData to Result.Loading. This disables and enables the Sign Up and Cancel buttons. Then, UseCase.invoke() jumps into the spotlight.

The cancel button is handled by doCancelSignup(). It first attempts to cancel any pending tasks launched if the optional local delay value was specified. Then it cancels the HTTP connection using cancelCall() in CancelableCallRepository. We override onCleared() to launch these same steps if the Fragment exits.

UseCase and DelayedScheduler

Depending on whether a delay is provided, SignupUseCase.execute() will run immediately or be scheduled via ScheduledExecutorService. In the latter case, we hold a reference for possible cancellation later.

SignupUseCase and CancelableCallRepository

The execute() method of SignupUseCase uses AuthRepository to make our service call. We provide an ID to identify the Retrofit2 Call; it is stored in a map managed by our super Repository class, CancelableCallRepository.

Retrofit and OkHttpClient

In AppModule, we use addConverterFactory() to allow the JSON service response to be painlessly deserialization via Gson into an instance of User. As demonstrated here, it’s also possible to make Retrofit pause during each request using an OkHttp Interceptor.

Done!

Disclaimer: I haven’t yet used these techniques in production, nor have I done analysis for leaks or performance.

The full source to this project can be found on GitHub.

If you found this useful or have improvements, please let me know below.

Check back for updates.