· 11 min read
MVVM with architecture components: a step by step guideline for MVP lovers
Ok, so now that MVVM is the standard to implement Android Apps since Google released their Guide to App architecture, I think it’s time to provide some easy information to understand the MVVM pattern from the eyes of an MVP user.
So, if you’ve reached here by chance, but don’t know what MVP is or how to use it in Android, I recommend you to first take a look at this article about the topic that I wrote some time ago.
You also have the option to watch the content on YouTube:
MVVM vs MVP - Do I need to refactor all my Apps now?
For a long time MVP has seemed to be the most popular presentation pattern to isolate the UI from the business logic, but now there’s a new cool kid in the game.
Many people asked me whether they should run away from MVP, or what to do when they start a new App. These are some random thoughts about it:
- MVP is not dead. It’s still a perfectly valid pattern that you can keep using if you used to do it before.
- MVVM, as a pattern, is not necessarily better. The specific implementation Google made makes sense, but there’s a reason why MVP was used before: it just fits pretty well with the Android framework and with minor complexity.
- Using MVP does not mean that you cannot use the rest of the architecture components. Probably the
ViewModel
doesn’t make much sense (as it’s the natural replacer to the presenter), but the rest of the components can be used one way or another. - You don’t need to refactor your App right away. If you’re happy with MVP, continue with it. In general, it’s better to keep a solid architecture rather than having all the new trends implemented in different screens of the App. Refactors like this one don’t come at no cost.
Differences between MVVM and MVP
Luckily, if you already know about MVP, learning the MVVM is extremely easy! There’s only one slight difference, at least in the way that it’s usually applied to Android:
In MVP, the presenter communicates with the view through an interface. In MVVM, the ViewModel communicates with the view using the Observer pattern.
I know that, if you read the original definition of the MVVM pattern, it won’t match exactly what I said before. But in the particular case of Android, and by excluding data-binding out of the equation, in my opinion, this is the best way to understand how it works.
Migrating from MVP to MVVM without Arch Components
What I’m doing here is to adapt the example I did for MVP (you can take a look at the repository here) to use MVVM. The new repository is here.
I’m excluding Architecture Components for now, so that we absorb the idea first. Then we can get into how the new “framework” that Google made up works specifically, and the points where it makes things easier.
Creating an Observable class
As we are using the Observer pattern, we need a class that can be observed. This class will hold the observers and a generic type for the value that will be sent to these observers. When the value changes, the observers are notified:
class Observable<T> {
private var observers = emptyList<(T) -> Unit>()
fun addObserver(observer: (T) -> Unit) {
observers += observer
}
fun clearObservers() {
observers = emptyList()
}
fun callObservers(newValue: T) {
observers.forEach { it(newValue) }
}
}
Using states to represent UI changes
As we now don’t have a way to communicate directly with the view, we can’t tell it what to do. The way I found more flexible is to have models that represent the UI state.
For instance, if we want it to show a progress, we’ll send a Loading
state. The way to consume that state is totally up to the view.
For this particular case, I created a class called ScreenState
, which accepts a generic type that will represent the specific state that the view needs.
There may be some specific states that apply to all screens, like Loading
(you could also think about an Error
state), and then a specific one per screen.
The general ScreenState
can be modeled by using a sealed class like this:
sealed class ScreenState<out T> {
object Loading : ScreenState<Nothing>()
class Render<T>(val renderState: T) : ScreenState<T>()
}
Then the specific states can have any structure we may need. For the login state, an enum is enough:
enum class LoginState {
Success, WrongUserName, WrongPassword
}
But for the MainState
, as we are showing a list of items and a message, the enum wouldn’t give us enough flexibility. So the sealed class is again extremely useful (we’ll see later why):
sealed class MainState {
class ShowItems(val items: List<String>) : MainState()
class ShowMessage(val message: String) : MainState()
}
Converting the presenters to ViewModels
The first thing we no longer need is the View
interface. You can get rid of it (and the View
argument), because we’ll use an observable instead.
To define it:
val stateObservable = Observable<ScreenState<LoginState>>()
Then, when we want to show a progress indicating that a process is running, we just call the observers with the Loading
state.
fun validateCredentials(username: String, password: String) {
stateObservable.callObservers(ScreenState.Loading)
loginInteractor.login(username, password, this)
}
When the login finishes, we ask to render the success:
override fun onSuccess() {
stateObservable.callObservers(ScreenState.Render(LoginState.Success))
}
Honestly, this previous state can be modeled in many different ways. If we want to be more explicit, we could say that it can navigate to main screen with a LoginState.NavigateToMain
or similar.
But as this depends on many factors depending on the App structure, I’ll leave it like that.
Then, in ViewModel’s onDestroy
, we clear the observers so that we don’t leave dead observers around.
fun onDestroy() {
stateObservable.clearObservers()
}
Using the ViewModel from the Activity
The Activity now cannot act as the ViewModel’s View, so that’s where the observer pattern gets into action.
First, create a property that holds the ViewModel:
private val viewModel = LoginViewModel(LoginInteractor())
Then, in onCreate
, you can start observing the state. When the state is updated, it will call the updateUI
method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
viewModel.stateObservable.addObserver(::updateUI)
}
Here, thanks to the sealed classes an enums, and by using the when
expression, everything becomes pretty easy. I’m processing the state in two steps: first the general states, and then the particular LoginState
.
The first when
will show a progress with the Loading
state, and will call another function if it has to render the specific state:
private fun updateUI(screenState: ScreenState<LoginState>) {
when (screenState) {
ScreenState.Loading -> progress.visibility = View.VISIBLE
is ScreenState.Render -> processLoginState(screenState.renderState)
}
}
This second one hides the progress (in case it was visible), navigates to the next activity if the login was successful, or shows an error depending on the error type:
private fun processLoginState(renderState: LoginState) {
progress.visibility = View.GONE
when (renderState) {
LoginState.Success -> startActivity(Intent(this, MainActivity::class.java))
LoginState.WrongUserName -> username.error = getString(R.string.username_error)
LoginState.WrongPassword -> password.error = getString(R.string.password_error)
}
}
When the button is clicked, we call the ViewModel
so that it can do its work:
private fun onLoginClicked() {
viewModel.onLoginClicked(username.text.toString(), password.text.toString())
}
And then, in onDestroy()
, we call to destroy the ViewModel (so that it can detach the observers):
override fun onDestroy() {
viewModel.onDestroy()
super.onDestroy()
}
Changing the code to use Architecture Components
For now, we have taken a tailor-made solution as a middle step to MVVM, so that you can see the differences easily. So far, there are no many benefits when compared to MVP.
But there are some. The most important one is that you can forget about the activity being destroyed, so you can detach from its lifecycle and do your work anytime. Thanks to the ViewModel
and LiveData
, you don’t need to worry when the activity recreates or when it’s destroyed.
Here’s how it works: while the activity is recreated, the ViewModel
stays alive. It’s just by time the activity is finished forever, when the onCleared()
method from the ViewModel
is called.
Taken from developers.android.com
As LiveData
is also lifecycle aware, it knows when it has to attach and detach from the lifecycle owner, so you don’t need to take care about it.
I don’t want to dive deeper into how Architecture Components work (it’s deeply explained in the developers’ guide, but I can write another article if you need it), so let’s continue with the implementation.
To use Architecture Components in our project, we need to add this dependency:
implementation "android.arch.lifecycle:extensions:1.1.1"
There are several libraries for architecture components and different ways to include them (depending on whether your using AndroidX or not, for instance). So if you have special needs, take a look here.
Architecture Components ViewModel
In order to switch to the ViewModel
, you just need to extend the class from the library:
class LoginViewModel(private val loginInteractor: LoginInteractor) : ViewModel()
Remove onDestroy()
, as it’s not required anymore. We can move its code to onCleared()
. So that way we don’t need to observe in onCreate
and stop observing in onDestroy
, just when the ViewModel
is being cleared.
override fun onCleared() {
stateObservable.clearObservers()
super.onCleared()
}
Now, moving back to the activity, create a property for the ViewModel
. It needs to be lateinit because it will be assigned in onCreate
:
private lateinit var viewModel : LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
viewModel = ViewModelProviders.of(this)[LoginViewModel::class.java]
}
That’s the ideal case, when the ViewModel
receives no arguments. But if we want that the ViewModel
receives arguments via constructor, you must declare a Factory
. This is the way (a bit convoluted, I know…):
class LoginViewModelFactory(private val loginInteractor: LoginInteractor) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return LoginViewModel(loginInteractor) as T
}
}
And getting the ViewModel
changes a bit:
ViewModelProviders.of(
this,
LoginViewModelFactory(LoginInteractor())
)[LoginViewModel::class.java]
Replace Observable with LiveData
The LiveData
can safely substitute our Observable
class. One thing to take into account is that LiveData
is immutable by default (you can’t change its value).
This is great, because we want it to be public so that observers can subscribe, but we don’t want to let other parts of the code change the value..
But, on the other hand, the data needs to be mutable, why would we observe it otherwise? So the trick is to use the equivalent to a private field plus a public getter with a more restrictive return type. In the case of Kotlin, it would be a private property plus a public one:
private val _loginState: MutableLiveData<ScreenState<LoginState>> = MutableLiveData()
val loginState: LiveData<ScreenState<LoginState>>
get() = _loginState
And we don’t need onCleared()
anymore, as LiveData
is also lifecycle aware. It will know the right time to stop being observed.
To observe it, the cleanest way is as follows:
viewModel.loginState.observe(::getLifecycle, ::updateUI)
Take a look at my article about function references if you find this line confusing.
The definition of updateUI
requires a ScreenState
as an argument, so that it fits the return value of the LiveData
and I can use it as a function reference:
private fun updateUI(screenState: ScreenState<LoginState>?) {
...
}
The MainViewModel
doesn’t require onResume()
either. Instead, we can override the getter of the property, and run the request the first time the LiveData
is observed:
private lateinit var _mainState: MutableLiveData<ScreenState<MainState>>
val mainState: LiveData<ScreenState<MainState>>
get() {
if (!::_mainState.isInitialized) {
_mainState = MutableLiveData()
_mainState.value = ScreenState.Loading
findItemsInteractor.findItems(::onItemsLoaded)
}
return _mainState
}
And the code for this activity is very similar to the other one:
viewModel.mainState.observe(::getLifecycle, ::updateUI)
The previous code seems to be a little more complicated, but that’s mainly because of the use of a new framework and until you understand how it works.
There’s certainly some new boilerplate, like the of the ViewModelFactory
, the ViewModel
lookup, or the two properties required to prevent LiveData
edition from outsiders. I simplified some of them in this article by using some Kotlin features, and they may help you feel more comfortable with the code. I didn’t want to add them here for simplicity.
As I mentioned at the beginning, whether you decide to use MVP or MVVM is totally up to you. I don’t think there’s an urge to migrate if your architecture is solved by using MVP, but it’s interesting to have an idea of how MVVM works because you’ll need it sooner or later.
I think we are still in a point where we are trying to find out the better ways to use MVVM with architecture components in our Android Apps, and I’m sure my solution is not perfect. So please, let me know everything that worries you or that you don’t agree with, and I’ll be happy to update the article with your feedback.
Remember that you have access to the complete code on my Github (some stars are always appreciated 😁)