Weighing in on the Holy Architecture War — My take on“Architecture Components”

Yonatan V. Levin
AndroidPub
Published in
10 min readMay 17, 2017

One of the great beauties of architecture is that each time it is like life starting all over again. — Renzo Piano

Even in my native Israel, where conflict is even more common than hummus, the biggest holy war right now is over the question of what is the best architecture, framework, language for Android: RxJava, Kotlin, Clean Architecture, MVVM, 100500 types of MVP, Viper, SOLID principles, DI.

But you know what? For all the clamor and heated debate, none of it truly matters, and I don’t mean that in some depressing Nietzschan way. What I mean is that you can have the latest, most advanced, “hyped” architecture that that cool guy with the sweet ‘do from the conference talked about…but still have 0 active users.

Today I’m going to weigh in on this holy war and give you my two cents on the best Architecture for Android, gleaned from years of experience in Android development for Gett and now KolGene.

Ultimately, my view on the matter can be boiled down to one, simple concept:

The best architecture is the one that YOU can easily understand, explain to others and rapidly iterate over new features with minimal code refactoring.

Mind blown, right?

In order to understand which architecture is best for you, you need to understand the pros & cons of each one and be able to analyze how your specific application and users can benefit from it.

Recently, some guys at Google released a set of libraries called “Architecture components” — a set of components that might make your life much easier. I was privileged to be able to get my hands on it early. I’m sure that there are a lot of blog posts out there describing these components but for me, the main question is how this new library can help me in my daily responsibilities developing applications for customers of my startup, Kolgene.

From my recent post about Offline support, you know that we are using the MVP pattern, and low-level architecture looks like this.

We’re using a View that delegates its interactions to Presenter, which in turn acts on data in the content provider and background service.

The main issue with MVP in Android is that there are 100500 different variants of its implementation: with “configuration change” survival, without configuration, with and without state management in Presenter, and View reference in Presenter. Using “Architecture components” can significantly improve your architecture, or at least it can be a really good starting point.

So, in my work with KolGene, before I jumped on any new Architecture (which was hard — trying new things is like crack to me), I decided to build a small app with the MVP pattern and see how easily it would migrate to “Architecture components”.

As I’m a big (“yuuge”) fan of Star Wars, the app that I built shows the list of all Star Wars movies. When the Application starts, it performs a request for a list of movies going from UI through Presenter class to Model layer. In our Model layer we have IntentService which downloads the list, parses it and returns it back to Presenter via BroadcastReceiver. Presenter then updates UI based on the received data.

The structure of the app is like this:

Full code on Github repository: https://github.com/parahall/star_wars_movies

In View layer we have our MainActivity which is responsible for UI only. Inside we’re using ButterKnife for binding Views to variables.

In our Presenter layer we have:
MvpView interface which defines some basic sets of behaviours for every single View.
MainActivityView interface with concrete View interaction methods.

BasePresenter class that holds base Presenter logic like attaching and detaching Views, creating HandlerThread to get any new interaction on background thread and release the UI thread (my personal preference is to retain Presenter through a static variable. Why? I just find it easier)

MainPresenter — the class that serves as mediator on our MainActivityView and Model. When View is attached, it requests from the service to be updated with the latest data, and once the data is received from Model it updates the UI.

(P.S. In reality I would suggest hiding all Android components behind Interface to allow for pure unit testable code for your Presenters and also to take out Adapter for better abstraction.)

Our Model layer holds our business logic and the POJO class of StarWarsMovie. The service downloads the list, parses into our list of POJO objects and sends it back to Presenter via Broadcast.

Phew! I hope you’re still with me because now that we have common ground we can start to step into the path of the Light Side and see what we can change with “Architecture components” … and a little bit of force :)

Architecture components

Documentation: https://developer.android.com/arch

Architecture components contains a couple of components

ViewModel — provides the data for a specific UI component (fragment, activity) and handles the communication with the business part of data handling — such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an Activity due to rotation.

This sounds similar to Presenter but with one big added bonus — the retention is built in so you don’t need to worry about how you retain it. Under the hood, the retention of fragment is happening with the help of FragmentManager & HolderFragment. I encourage you to check the source code — it’s beautiful..the Helen of Troy of the coding world. #TechPorn

LiveData is an observable data holder. It allows observing changes to the data it holds across multiple components of your application without creating explicit and rigid dependency paths between them. It respects the complex lifecycle paths of your application components (activities, fragments, services) and does the right thing to prevent object leaking so that your application does not constantly consume more memory and lead to overall system problems due to memory thrashing.

But wait…this is all looking pretty familiar right about now. Yup, you’re right…it sounds a lot like RxJava but without its complexity, and of course flexibility. The biggest benefit of using LiveData is that you don’t need to handle lifecycle events and stop data streaming when the observed object is in a stopped state. Similar to RxJava, we can apply transformations on data that is streamed and better serve the observers.

Room is an object mapping library that provides local data persistence with minimal boilerplate code. At compile time, it validates each query against the schema, so that broken SQL queries result in compile time errors instead of runtime failures. It abstracts away some of the underlying implementation details of working with raw SQL tables and queries. It also allows observing changes to the database data (including collections and join queries), exposing such changes via LiveData objects. In addition, it explicitly defines thread constraints that address common issues such as accessing storage on the main thread.

If you ever wrote SQLite DB in Android you’ll know the agony this avoids. Getting compile-time errors for SQL is a big benefit, as is the ability to expose LiveData objects for data streaming.

And the last component is

LifecycleOwner is a single method interface that denotes that the class has a Lifecycle. It has 1 method, getLifecycle(), which must be implemented by the class.

This class abstracts the ownership of a Lifecycle from individual classes (for example, Activities and Fragments) and allows writing components that can work with both of them. Any custom application class can implement the LifecycleOwner interface.

If you need a component that is aware of Lifecycle — this interface is for you. It’s very easy to have all Activities and Fragments implementing this interface by extending LifeCycleActivity/LifeCycleFragment (later, when API reaches 1.0 version it will be implemented by Activity/Fragment). This will give you the ability to be notified when the state is changed without querying the lifecycle owner (e.g. Activity) about his state.

I encourage you to read more about “Architecture components” in the Official Android documentation. For the rest of the article I will assume that you are already familiar with the various components and their methods

Moving from MVP to MVVM.

If you’re looking at these components and how they should play together you will realise that MVVM is the most suitable pattern to use, especially if you will look at ViewModel as state of data. MVVM is usually used with DataBinding where data is bonded directly to the view components. It eliminates boilerplate code that developers need to write the assigned data in ViewModel to the right UI component in View (LiveData hint hint).

The main difference between MVP and MVVM is that for MVVM, View is responsible for its own interactions and contains knowledge of data bound objects. But with LiveData transformations we can reduce it to a minimum. But the rest is applied to View — it’s stateless, and a direct reflection of what ViewModel is serving it. In our case, we are going to do that with the LiveData component.

ViewModel serves as a data manipulator -it receives events from View, defers the request to the Model layer, receives a response from Model, converts it to the most suitable format for UI (for example turning list of users to list of users names) and updates UI via DataBinding (in our case it will be LiveData).

In the case of our StarWars application, the architecture should look like this:

You can watch full code here on Github repository: https://github.com/parahall/star_wars_movies/tree/feature/mvvm

MainActivityView starts and creates MainViewModel, binds views to variables with ButterKnife and sets the initiation state of views. After initiation is complete, it subscribes itself to data updates from LiveData in MainViewModel. And of course, we need to move our UI-related stuff (e.g. ListView Adapter) back to MainActivityView.

ViewModel​ creates request data updates from Model and stores LiveData<List<StarWarsMovies> object obtained from Room DataBase by calling loadMovies(). From this point on, every time the database is updated, our live data object in ViewHolder will be updated too.

At this point I should mention that I used object mDb without creating AppDatabase. Since our Database is used across all applications it was necessary to have a global process variable of it — Singleton. The best way to handle its creation and lifecycle is to use a Dependency Injection framework like Dagger 2.

The logic of database creation lives in AppModule:

The AppDatabase is an abstract class that extends RoomDatabase, and most of its code is generated in compile time. AppDatabase holds all DAO objects that we want to store — in our case StarWarsMovieDAO.

DAO stands for Data Access Object, an interface class where you define interaction with the database and is generated at compile time.

Don’t forget that `loadMovies() output is LiveData object. This is what allows us to bind our ViewHolder to be updated automatically when the data is changed in Database.

And finally, we change our service; instead of sending Intent to Presenter via BroadcastReceiver we insert Movies into RoomDatabase.

Well, the benefits here are obvious:

  • No need to retain Presenter/ViewModel, it’s done for free.
  • Instant update of Data in UI and ViewModel
  • Very easy implementation of cache mechanism (Temporary DataBase that deletes upon process kill).
  • SQL compile time verification
  • Lifecycle awareness
  • Less Code

And naturally, there are downsides:

  • MVVM is a great pattern, but now part of the UI logic is moved back to View, meaning less testable code. Yeah, you can test it with Espresso, but hey, what are you going to do with 1000 tests, not to mention the time they take to run.
  • What happens when you observe on 10 different objects? Are they all in memory at the same time? You don’t have control over the large objects (e.g. list of 1000 items). If you want lazy loading you will need to take care of it separately by returning Cursor. Most of this will be addressed in the 1.0 release.
  • In a world with low-end devices with a small amount of heap — it could be an issue.
  • Content Provider is something that is widely used for content sharing applications. It will require additional effort to make sure both LiveData & ContentProvider are working together. In order to make it work you will need to make Room return Cursor.

Does all this mean that you should go off and change your entire app to the new Android architecture? Well, it depends on you.

As for me, the KolGene app will stay with its current architecture using ContentProvider and UI updates with ContentResolver.

For new, upcoming features, I will try to use the new MVVM approach with “Architecture Components” to better understand the benefits and how it plays with the rest of the Android world, especially using ContentProvider alongside LiveData.

So there you have it — my take on the polarized architecture debate. Now you know all the pros and cons, you have a force. Use it wisely :)

Thanks for reading. If you liked it, please give me your and share this. I’ll also love to hear your comments and suggestions :) Thanks

--

--

Yonatan V. Levin
AndroidPub

R&D Tech Leader @ monday.com by day, Founder of Android Academy in between