February 1, 2020
Updating our app architecture was a great challenge for us. We had to make sure that we used the right framework and the right Android library. Above all of that, we were deciding if we wanted to shift to a new programming language based on the JVM. So, after long and exhaustive rounds of discussions, we settled on using MvRx (pronounced as Mavericks) as our Android framework. Moreover, we decided to shift to the Kotlin language because it offered many distinguishable benefits like flexibility in the process of code writing. In this blog, we will detail why we shifted to Kotlin and started using MvRx as our Android framework. Moreover, we will discuss why we started using the WorkManager Android library instead of sticking to our traditional methods.
One of the biggest decisions we took while revamping our app architecture was shifting to a whole new language. Granted, we had decided on using MvRx as our Android framework and that necessitated the use of Kotlin. In fact, MvRx is Kotlin first and Kotlin only. However, adopting Kotlin offered a whole range of other benefits for us.
In fact, even Android is going Kotlin first. This was announced at this year's Google IO event. It means that going forward, Kotlin is not just an officially supported language for Android development, it is also the primary language for Android development. For example, the upcoming Jetpack Compose UI toolkit is compatible only with Kotlin, and most Jetpack libraries offer ktx-variants for idiomatic usage in Kotlin code.
We soon realised that it was more prudent for us to go for Kotlin because it is based on the JVM platform and environment. It offered interoperability with Java which allowed calling Java code from Kotlin and vice versa. This meant that we could run out existing Java-based projects in this language. This was extremely helpful in the type of insertion in coding.
Kotlin, just like Java, is an object-oriented programming language with some functional programming traits as well. This meant that the language constructs were familiar to us, and we could transition from imperative Java code to declarative Kotlin code gradually. This syntax was rather familiar to us and made it easy for us to modify code and identify errors. When we started using Kotlin, we identified many benefits of putting it into use. Some of them are listed below:
1) Data Classes
Creating data model classes in Java is a tedious process, and implementing essential methods such as equals(), hashCode(), copy() and toString() correctly is difficult. Kotlin provides us with Data classes to solve this problem. It makes writing immutable model classes very easy, and automatically generates correct implementations of these methods.
2) Kotlin Coroutines Benefits
Kotlin coroutines can interact with RxJava when needed. We could do this by following a simple pattern or following the instruction described in the guide to reactive streams with coroutines. This significantly reduced our learning curve.
Most importantly, Kotlin coroutines are very lightweight and efficient. In our testing stages, we realized that the amount of memory used by RxJava was generally far higher compared to coroutines. This led to a slower app given the higher CPU usage for the garbage collection of all the objects generated by RxJava. Moreover, it led to higher battery consumption. With the use of Kotlin coroutines, we were able to solve this problem to a certain extent.
After figuring out the language we were going to use, we moved onto one of our biggest challenges yet. We needed to move to a whole new android framework. After lots of discussions and evaluating our needs, we settled on MvRx.
This android framework proved to be the perfect solution because it catered to every one of our technical needs while being the easiest to implement. Here are some of the reasons why we believe choosing MvRx was the right decision for us.
1) State Management
Before we started using this new framework, we had set certain rules for ourselves. We wanted a unified way of handling state updates and persistence allowed us to move faster and navigate different sections of the codebase with ease.
Furthermore, MvRx also enforces immutability of state and a unidirectional data flow pattern. This made it easy for us to manage state even in the most complex screens.
We needed to make sure that the framework we were using was “Bharat-Ready”. This meant that our app should be able to run on the low-specs phone. Moreover, we needed to ensure that it worked effortlessly on phones with limited RAM to the points that they needed to handle state persistence even in the event of “process death”. MvRx allowed us to do that without any hiccups.
2) Lifecycle Handling
Now that we knew MvRx could take care of our concerns related to state management, we focused on some other issues we had related to our app architecture. We wanted a framework that allowed us to write code without worrying about the complicated lifecycles in Android. MvRx made sure that our RxJava streams were properly disposed when the ViewModel was cleared. Moreover, In Fragments, it made sure that the state updates were delivered only when the Fragment could consume them, which is after they are completely created.
While MvRx took care of these issues, we could focus on feature development rather than getting bogged down in the tedious work of handling lifecycles.
3) Cleaner architecture
MvRx allowed us to naturally shape our architecture towards an MVI design paradigm. Resultantly, the views in our apps only contain logic related to how they should render themselves according to the state.
Essentially, the ViewModels contain the logic of controlling that particular screen and handle state. They serve as the natural point for executing business logic contained in our repository layer since they have a very simple lifecycle and own the state. The repository layer contains many classes containing our business logic in extremely clean, well-separated units.
4) Easy Multi-Threading
It is a well-known fact that Threading is considered rather challenging on Android. However, MvRx helped us perform asynchronous tasks without any hassle. It did so by ensuring that all state updates were delivered to the View for rendering only on the main thread. Moreover, the state mutations and the business logic Rx streams were processed off the main thread. This translated into a junk-free app experience to our users.
However, even after adopting the Kotlin language and the MvRx framework, we were still facing one major issue. We wanted a one-stop solution to run all of our background work. This turned out to be another major step towards revamping our app architecture.
WorkManager is an Android library that catered to all of our needs and ran the deferred background tasks even if the app exits. It provided us with an API that was battery friendly and took care of compatibility issues. Moreover, with WorkManager, we could schedule both complex dependent chain of tasks and periodic tasks.
Apart from this, WorkManager proved helpful for us in a lot of ways:
1) It provided us with guaranteed and constraint-aware execution. In other words, we could now schedule a task depending on the condition when it should run. Consequently, WorkManager took care of all the constraints and ensured that they will be executed even when the device is rebooted or even if the app is exited.
2) It provides backward compatibility which means that we did not need to figure out the device capabilities or choose an appropriate API. We could hand off the task to the WorkManager. It chose the best option for the execution of the task.
3) Next, WorkManager supports tasks query. We could not only schedule the task but also check the status of the state of the task. This provided us with information on if the task was running or if it had quit or had succeeded or failed.
4) WorkManager also supports task chaining. We could now create drafts of our work and enqueue them one after the other using the Work Manager.
After endless rounds of discussion and through implementation and changes in our app architecture, we were able to work with a much smoother and efficient app. While the revamping process was time-consuming and rather challenging, we were able to overcome all of our challenges through consistent team efforts.
This blog was authored by Balram Pandey and Kshitij Chauhan. It was drafted by Etee Dubey.