We all heard of the architecture components library. However, if our app is built with an MVP architecture or MVVM architecture, there are specific barriers to using it.
How can we cross these barriers?
Please note that architecture, in general, is abstract, and apart from some ground rules, it can be applied in different ways. Therefore, read this article as a means of new ideas or approaches rather than a single way to do things.
In the beginning, there was no architecture. Making applications were the name of the game back then.
However, as time passed, there came an understanding that the code base needs to be maintainable. As an application grows, so does its complexity. Using god activities made everything very coupled and hard to maintain. Sometimes even the smallest feature could break the entire system.
A skyscraper cannot be built without blueprints and planning. There needs to be a definitive plan for each step of the way — from the infrastructure all the way to the antenna up top.
Applications are no different. Applications need a blueprint as well, and that blueprint is known as architecture.
The first architecture that gained popularity in the Android world was MVP. There was a clear separation between a view, its business logic, and its data. Every component had its way and its purpose.
Later on, Google saw the change in the Android world, and it has provided us with its own set of tools to ease our lives. It introduced to us the architecture components libraries.
MVP matured well and is still being used in the industry. Architecture components did not introduce a superior architecture. MVVM is more of a rival than a supreme ruler, and it should be treated as such.
However, Architecture components did introduce new tools that seemingly work better with MVVM. Is it the case?
As I mentioned earlier, architecture has different implementations and interpretations. Therefore, some would prefer to stay with MVP, and some would migrate to MVVM.
I will tackle both ways.
This route will be picked when most of the application is already written with MVP in mind, or when MVP is the clear architectural choice.
Architecture components are a great tool, but we don’t want to change our architecture. We want to have the best of both worlds.
In this article, I will use a classic application. The application will display a list of opinionated quotes that are retrieved by a remote API.
Each architecture has its flow of data, and the MVP data flow is as follows:
The data resides in a data controller or repository class. According to the business logic, the presenter requests data with a callback. Once the data is ready, the presenter is notified and according to its business logic activates the view to display the data.
At the end of the data flow chain in our application, there is an adapter. Few are the applications that exist in the market that don’t have at least one screen showing a list of data. Therefore, we can assume that most applications will have at least one adapter.
Adapters are a weird kind. Instead of the presenter, giving orders to the view, the adapter asks for orders. This weird flow was accepted as an anomaly in MVP because of the way adapters work. Consequently, the presenter will implement the following methods:
- onDataReceived(data: List<Object>) \ onDataChanged(data: Object) \ onDataDeleted(data: Objecsubmit list)…
- getDataCollectionSize(): Int
- getItem(position: Int): Object
With version 27 of the support libraries, a new kind of adapter was introduced to save us from writing all of this boilerplate code. This adapter is called ListAdapter, not to confuse with the API 1 ListAdapter.
The way ListAdapter works is by passing it a class that implements the DiffUtil.ItemCallback<T> interface and declaring the data and view-holder object types. Each time our data has changed, we call a dedicated method to submit the data collection anew. Behind the scenes, the adapter uses the class that implements the DiffUtil.ItemCallback<T> interface to analyze the data and to act upon only what has changed. As a result, we don’t need to worry about all the boilerplate code that we once had.
However, our presenter is still left with one single method:
- onDataReceived(data: List<Object>)
It might not seem like a big deal, but remember we are talking about a simple application in this example. Rare are the cases where such a solution is sufficient. Most complex applications have multiple streams of data, and it adds up to more unnecessary boilerplate code.
How can we use architecture components to our advantage?
LiveData is observable data. Instead of actively waiting for a data response, we subscribe to it to be notified of any data change. There are two ways to subscribe to data, and the names are self-explanatory:
- Lifecycle aware
I will focus on the second subscription type since it is better tied to this article.
To subscribe to LiveData, the obvious thing we need to pass on is a callback class. However, the second part is quite peculiar — we need to pass on our activity or fragment.
Why do we need the view class? Why can’t we subscribe to LiveData from our DataController class and notify the presenter of each data change?
The answer lies in the subscription type name — Lifecycle.
With the architecture components library, under the hood changes were introduced to Android. These changes are what allows the new architecture components to function.
How do these changes tie in with LiveData?
The changes to work with LiveData were introduced to two particular classes:
- FragmentActivity, from which AppCompatActivity inherits from
- Fragment, from the support libraries
These classes implement an interface called LifecycleOwner. When we subscribe to LiveData observation, the method expects to receive a class that implements that interface alongside our callback. It constructs a map entry of our observer callback as a key pointing to a wrapper class.
There are two types of wrapper classes:
The wrapper is created according to the observation choice. In our case, a lifecycle observation is followed by the creation of an instance of LifecycleBoundObserver. The class holds two member classes:
Afterward, the wrapper class is being saved in an instance of a class named LifecycleRegistry which is extracted by calling the single LifecycleOwner method, getLifecycle(), which is part of our screen class. It constructs a map entry that resides in the LifecycleRegistry instance class. The entry is built from our previous wrapper class, LifecycleBoundObserver to a wrapper class that has two values:
- LifecycleObserver — our LifecycleBoundObserver instance
- State — the current lifecycle state of our Activity \ Fragment
Each time a change occurs in the lifecycle of our screen, the LifecycleRegistry is the one that receives the changes. Each time our screen is destroyed but retained, the LifecycleRegistry instance is saved by being passed in a bundle and read in the onCreate method.
Now that we understand how LiveData is built under the hood, what would we need to implement it in our presenter?
In our opinionated quotes application, we have one single source of data, coming from a remote API. Thus, we will only need one instance of LiveData. Two components will be necessary, as we’ve seen in the explanation above:
- LifecycleOwner — our screen
- Observer — the callback of the data
The architecture in hand is MVP, so no data binding in the screen class is allowed since it needs to be passive. Furthermore, it cannot be in the presenter itself, since it is not supposed to be familiar with Android components nor deal with data.
Our ideal class is our data controller. The data controller class only needs to know about a class implementing the LifecycleOwner interface and a callback class or method. Therefore, it will hold a single method that would receive two parameters:
- LifecycleOwner instance
- Callback class or method instance
To retrieve the LifecycleOwner instance of our screen, we can create a method in a unique interface from which all screens implementing the view portion of the contract will inherit. Each class will be responsible for retrieving itself as a LifecycleOwner instance.
At the appropriate time of initialization, the presenter will call a dedicated bind method in the data controller. The callback class will be received from the screen, and the LifecycleOwner will be extracted from the view by the presenter.
As a result, the adapter is bound directly to the data, and no redundant code exists in the presenter. The presenter was cut from the data flow, and the data flow depends on the presenter only for the initial binding.
Our adapter will now be updated of any changes and act upon them, not relying on the presenter but on the data itself.
However, the data is lifecycle aware, but our presenter is not. Once the screen is flipped, the presenter is recreated, and all the previous information we have is lost.
Lifecycle handling used to be a synonym with boilerplate and a lot of headaches. It is imminent when we look at the current top applications in the market, most of which are locked to portrait mode.
How does the architecture components library alleviate lifecycle handling?
It provides us with a class named ViewModel.
The ViewModel class has a broader lifecycle than our screen. It survives orientation changes and thus, saves us from weird data transfers during the occasion.
Consequently, our screen cannot be the one who initializes a ViewModel, since it has a narrower lifecycle. Thus, a factory class exists to create or fetch from the cache the requested ViewModel instance. Even with the default configuration, in which no factory is mentioned, a default one is used behind the scenes.
Therefore, our presenter class will have a similar lifecycle to a ViewModel class. However, unlike a ViewModel class, the presenter holds a direct reference to our screen, which could result in a memory leak.
As a result, the presenter could communicate with different instances of the same screen, depending on how many times the screen is destroyed and recreated. Additionally, the presenter will not be directly instantiated by the screen but by a factory, to allow for a broader lifecycle. Therefore, the screen will be removed as a constructor argument and added in setter method.
Also, the screen instance cannot be preserved in the presenter when the activity or fragment is destroyed since that would lead to a memory leak. The simplest solution is the one that requires less code and would save us from clearing the instance each time the screen is destroyed is WeakReference.
The screen will be wrapped in a WeakReference class when it would be attached. Once it is destroyed, the garbage collector will clear it for us.
Now that our presenter is ready to have a broader lifecycle, how do we apply it?
In our application written in the MVVM path, the ViewModel is extracted in a certain way:
After a neat, readable line, we will have a ViewModel instance. It looks like magic until we understand what happens under the hood.
For us to understand it we need to separate it into two parts:
- ViewModelProviders.of(screen ,factory)
- .get(ViewModel class)
Why is our screen necessary for the ViewModel call? Under the hood, changes were introduced to allow the feature to work. The changes, similarly to the LiveData changes, were introduced into two classes:
- FragmentActivity, from which AppCompatActivity inherits from
- Fragment, from the support libraries
What is that change? Our screen holds a reference to a new class that is responsible for storing our ViewModel classes — ViewModelStore.
Once our screen is rotated, the ViewModelStore instance survives rotation by the usual way of saving it in the onSaveInstance and retrieving it in the onCreate method. Also, if the screen is destroyed, ViewModelStore instance is destroyed along with it.
The ViewModelStore class is a simple container class that holds a map of String keys to ViewModel instances.
The screen implements an interface named ViewModelStoreOwner which has a getter method that retrieves our ViewModelStore instance. Thus, the first argument of the first part requires a class that implements a ViewModelStoreOwner interface, namely the mentioned fragment and activity up top.
The factory instance is necessary for the second part, which creates a wrapper class. The wrapper class is named ViewModelProvider, and it holds:
The get method inside the ViewModelProvider class retrieves a ViewModel from the ViewModelStore or creates one anew if it does not exist yet. To create a ViewModel class it calls the factory class, and once the ViewModel is created, it is stored in the ViewModelStore class.
The default factory initializes a ViewModel class through reflection. As a result, if a ViewModel class has at least one argument in its constructor, the factory cannot instantiate it through reflection. Thus, in most cases, we will need a custom factory for our ViewModel classes.
I recommend using Dagger2 for the custom factory creation since it would allow different presenter classes have different argument constructors all working with a single custom factory. The way it works is the same as with ViewModel, and there are plenty of guides out there. The only difference is the naming convention which calls everything a presenter. The repositories I have created, which are linked at the bottom of the article, show how it can be done.
Now that we understand how it works behind the scenes and we have our custom factory ready, all our presenter has to do is have its base class extend ViewModel.
An additional method we gained is the onCleared method that is called each time the screen is destroyed, which could be great for clearing resources.
MVP Route Conclusion
Thanks to the hard work Google has put into adding under the hood changes and creating the architecture components library, we can save ourselves from the usual hassle of boilerplate code without a lot of headaches. MVP can work very nicely with architecture components, and at first glance, no one would even notice you are using them.
The example project for this part has a few different branches, all of which display a specific step in the modification of an MVP project to an MVP Architecture Components project.
Contribute to TSurkis/AppinionMVP development by creating an account on GitHub.
Why does this section exist? The architecture components library works with MVVM like a charm.
It does, on paper. However, when you first start implementing the architecture, you can’t help but notice that something is missing.
The ViewModel is de-coupled from the screen class. However, once a screen requests data, the ViewModel class cannot call the screen to display the data. If the ViewModel is aware of the screen, then we are implementing an MVP architecture.
How can our screen with its list of quotes display the quotes once the ViewModel process the response? How can the ViewModel communicate with the screen without knowing it at all?
Just as we looked at architecture components with MVVM as our example to implement it in MVP, so we can take a look at MVP in this section to implement MVVM.
How is it done in an MVP architecture?
The presenter does not know the screen directly, but by an interface it implements. However, the ViewModel cannot know the screen even by an interface since it would be an MVP architecture all over again. The answer lies in the most underestimated feature of MVP — the contract.
The beauty of MVP is that the entire screen logic is understood by reading the contract. We could design all of the screens features just by writing the contract. As a result, the contract defines our screen and its various states. Consequently, our ViewModel needs a class to describe the screen state.
To understand the screen state class, we need to ask a big question. In all architectures exists a single M letter nobody gives a second thought about.
A lot of people reading this article will probably say that it is the data that represents the screen or the data controller class that process the data. However, when a thought is put into it a lot of questions appear:
What if we have more than one source of data or more than one data controller? Does the M represent each one of the sources or does it represent the data or data controllers as a whole? What about screens that have no data at all?
To clarify, we are discussing architecture in this article. As a result, there is no definitive answer to any architecture but different interpretations of the same idea.
After I read an interesting article by Hannes Dorfmann in which he questions the purpose of the M word, I understood how to implement MVVM with a flavor of MVP.
Reactive Apps with Model-View-Intent — Part1 — Model
Once I have figured out that I have modeled my Model classes wrong all the time, a lot of issues and headache I…
What if the M in each architecture, namely in MVVM, is the screen state? To put it differently, if we take the view part of our MVP contract and convert it to a data class, we would create a screen state class.
To keep the de-coupling of the screen from the ViewModel, we can leverage our knowledge of LiveData and wrap it around our screen state.
Our ViewModel will have a mutable class to change each time the screen business logic dictates a change. On the other hand, the screen would have an immutable class to observe and act upon on each change. Of course, applications are more complicated than that, but generally, this idea can be grown into different screen states for different screen parts.
Moreover, it would allow us to easily test our screen or our ViewModel since one does not require the other.
Also, we would need to differentiate data from screen state data. In our application, the data of the quotes is separate from the data of the screen state. The two are not supposed to be related since they display different data types. The adapter acts upon the quotes data changes separately from the screen acting upon screen business logic.
As a result, migrating an MVP architecture to MVVM wouldn’t be that difficult in theory. The contract will be translated into a state class, and the data would be separated from the states. Consequently, the presenter will be de-coupled resulting in an MVVM architecture.
The example project of this section exists in GitHub and has similar functionality to the MVP project but with an MVVM architecture in mind:
Contribute to TSurkis/AppinionMVVM development by creating an account on GitHub.
The architecture components library and the changes that were introduced for it to work are a blessing for us developers. Whether you are using MVP or MVVM or even other architectures, adapting the architecture to work with architecture components is very much achievable.
So have no fear, architecture components are here (for everyone).