Android Jetpack is a set of libraries that were packed together to give us developers a consistent API under the same umbrella. The majority of Android applications out there already use some of those libraries, such as Support Fragment, AppCompat or Support Test. In this post, I’ll focus on Gett’s use of the Architecture Components of Android Jetpack.

What We Used Before Android Jetpack

In order to better understand the reasons that caused us to adopt Architecture Components, we need to first understand what we had before. At Gett we have 2 applications; one for the driver and one for consumers. The driver app is our drivers’ main tool to get jobs, and as long as they work the app needs to be working. The other app is used only when a consumer wants to get from point A to point B, or to get on-demand Delivery. In both cases we used Model View Presenter (MVP) pattern.

MVP Implementation

For each screen we write a contract between the View and the Presenter, the Presenter side declares on all user interactions that it could handle and the View side declares on all the UI that it can render.

Our old contract in Java

The Fragment or the Activity implements the View side and the Presenter implements the IPresenter, the View hold a reference to the Presenter and the Presenter holds a reference to the View and everything is clean, elegant and great…well almost great since the Presenter holds reference to the Activity/Fragment, a Context leak might occur so we need to keep the View inside the Presenter as a WeakReference, so now we have to check every time before referencing the View, whether it exists, so our code now has a lot of “if view exists” statements.

The code inside the Presenter can be run in background threads, and since it is not possible to change View attributes from non UI thread we had to verify that the code inside the View will happen in the UI thread. The way that we did this is to wrap the View’s related code in the Activity/Fragment with Activity.runOnUiThread so our View implementation is wrapped with Runnable implementation. The tradeoff of wrapping the View and Presenter code is quite good for the advantages that we were getting by using this pattern.

Downside Of Our MVP Approach

The origin of all the problems that I’ll raise here is the awareness lack of the Presenter about the Lifecycle of the Activity/Fragment. The problem here is the Presenter might try to change UI while the Activity/Fragment can’t actually handle it. For example, the well known IllegalStateException might occur when the Presenter trigger some Fragment Transition after Fragment is not attached to the Activity.

One attempt to solve it is by passing to the Presenter some lifecycle events. For example, the Presenter will have onDestroy method which will clean the View’s WeakReference so will never call to the View. This means the Presenter now needs to also handle lifecycle events which violate the single responsibility principle, what is even worse is that it’s not guarantee that the Presenter will not called to the View since as I mentioned the Presenter code run at least by 2 different threads, the UI thread and some background thread and when referencing the same class’s member from a different threads we need to synchronise / use volatile members.

One even harder problem is the fact that in the View we post the job in the main thread Looper so by the time that the Presenter posts the message we are in a valid state but until the time that the View will actually handle this message we could be in an invalid state, good luck solving this kind of issue.

LiveData To The Rescue

After Google I/O 2017 when Architecture Components were first introduced, we started to look into LiveData. It seemed to address all our pain points and as a bonus helped us use a much more reactive approach. The main problem we had of holding a WeakReference to the View from the Presenter, was gone. The Presenter set new values to the LiveData and the Activity/Fragment observe changed in the LiveData.

Another benefit we saw was that the Presenter is no longer needed to be aware of the Activity/Fragment lifecycle since the LiveData is lifecycle aware out of the box. This means the Presenter can just set the values without thinking on the lifecycle but the View will observe those changes only when it is a valid lifecycle state. No more bugs occur due to invalid lifecycle state, and as a bonus the single responsibility principle is working well since we have class that only care about the presentation of the current state of the screen. This also helps us to keep the code in the Presenter clean without redundant code.

Another issue was that we needed somehow to do the View‘s related code in the UI thread, LiveData expose setValue method to a new value when the call is from the UI thread, but also postValue which take the call from any thread and handle it in the UI thread, with using of the postValue our View code actually become cleaner.

From a testing perspective both of those approaches are relatively the same, so with LiveData we got much cleaner code with great support to single responsibility principle. Many bugs that occur and cause us to write a workaround for it are gone.

Our new contract in Kotlin

Using ViewModel

In our consumer app we don’t support orientation changes but in the drivers’ app we do. Drivers can install the app on a Tablet and change the orientation, which causes a configuration change. ViewModel knows how to handle configuration changes so we no longer need to worry about it.

After a driver rotates the device, the combination of ViewModel and LiveData gives us the additional advantage of having the current screen state waiting in the LiveData, so we can retrieve this state immediately without the need to restore the data from some persistent storage or to reinvent the wheel. It’s just works out of the box.

Make Objects To Be Lifecycle Aware

At Gett we have adopted Kotlin and find most of our developers prefer to write new code with Kotlin. While exploring the language we encountered Kotlin coroutines, which is a way to write asynchronous code. Basically the compiler hides the callbacks so we are able to express our code sequentially and make the code much more readable.

For us usually the code inside the coroutines is part of the domain layer but we wanted some time to stop this code from running when the corresponding screen is not displayed anymore or in another word make this code lifecycle aware. We use LifecycleObserver to make the coroutines aware when the Activity/Fragment is stopped and cancel the job that it is doing.

One of the basic building blocks of Kotlin coroutines is the launch method, it getting some lambda as a parameter and it running this lambda in a background thread, the return value of launch is an object which called Job so we can take the Job and try to cancel it when needed.

Example of using LifecycleObserver with Kotlin coroutines

Now we can add the above JobCanceler to some LifecycleOwner and doing that the background can be easily close it self when the work that it doing is not relevant. The lifecycle aware helps us to produce much more clean and elegant code.

Some Concrete Examples

In our driver app, all new code is written with LiveData, ViewModel, and Lifecycle but we also used it while refactoring one of the major features that we have, the login flow. The change was trigger due to UX needs but we had a lot of pain point there. The way we did it before was using a state machine to manage all possible states during the login process for the domain side, for the UI side we use MVP as I describe before, after the refactor we used Repositories, Lifecycle Aware objects and Interactor that doing all the business logic and LiveData and ViewModel for the UI side, we didn’t have a lot of crashes in this flow but we had several workarounds that from time to time required some patches especially around orientation changes during the flow. Part of those workarounds, was just wrap code with try and catch and log those exceptions in order to fix the root problem some of them we fixed and some not, those logs happen in several places and affect 30% of our drivers, those issues were very hard to find due to the complexity of the flow and possible states. In the new way all issues that we encounter are easy to reproduce and more important even though we had much more logic, the code is much more scalable, much more understood for developers that look at the code in the first time and the fear to touch this code is gone. In the new features that were written in this way we saw notable degradation in the code stability and bugs compared to new features that we wrote in the previous way, and the time to fix issues is less than in the previous way.

Future adoption Plan

We also have plans to adopt Navigation and WorkManager. In our consumer app we have a lot of DeepLinks for integrations with 3rd party apps and services. We’ve started to explore the Navigation library; it looks awesome and we are waiting for the right time to start using it.

Both of our apps will need some solution as WorkManager. Currently we have our own solution, but when we started to adjust our apps to target Android Oreo, background restrictions changed some things. Currently we require Google Play Service to be installed in order to work with our apps so we are using Firebase JobDispatcher. However, we believe WorkManager will give us a solution that allows us to be more flexible in the future.

Summary

Android Jetpack Architecture Components has been critical for us. It’s already helped us produce clean and elegant code with fewer bugs, and helped us to do things that were previously very difficult to do. The integration between different components is very smooth and it saves us a lot of boilerplate code that we used to write by ourselves.