According to this article:
The ViewModel should expose states for the View, rather than just events.
I'm reusing viewmodel because I need the same data for multiple views, but my doubt here is that they use the same data but they display it in different ways. Like for example, if I have a list of users, the first view displays them, the second view use the data for sorting purposes, the third view displays some label if the list of users reached a certain size.
If I only expose the data (list of users) and the view will decides what to do on it, aside from I'm violating the architecture, it is also hard to test because I need to mock android, but I only want to test if a certain method is called, it doesn't matter how the view displays it.
So I'm thinking of solution like creating a State class for each view that uses the viewmodel so the viewmodel will update the state and I can easily now test if state changes.
My problem here is the more views reusing the view model, the more state for each and every view, it doesn't look right to me also, just imagine the method changing all of the states even though only 1 view at a time can be displayed.
Creating separate viewmodels for each view looks like duplicating the code for me, like for example: ResetPasswordView
and CreatePasswordView
, they do both have same process, almost same behavior also, so why not reuse viewmodel? ... or should I? What am I missing here?
Edit (my current solution):
From what I did, I've created separate view models for each fragment/activity because although they use the same data, they represent it differently. And by doing that, I can make unit tests for the presentation logic because all the data manipulations (specifically for the view) are happening on the view model.
I do share view model but for the purpose of navigation, example is the best way to explain this (I'm using Koin here as my dependency framework):
OnBoardingActivity:
class OnBoardingActivity {
var fullNameViewModel by viewModel<FullNameViewModel>()
private fun initViewModels() {
fullNameViewModel.stateShowEmail.observe(this, Observer {
// do navigate to email because that's the next screen/fragment after FullNameFragment
})
}
}
FullNameFragment:
class FullNameFragment {
var viewModel by sharedViewModel<FullNameViewModel>() // notice that I share the view model from the activity here to have only 1 instance
private fun initViews() {
RxTextView.textChanges(etFirstName)
.doOnNext { viewModel.setFirstName(it.toString()) }
.subscribe()
// ... set the other fields
}
private fun initViewModels() {
viewModel.stateValidationFirstName.observe(this, Observer {
when(it) { // validation
is RequiredValidation -> // show some error/validation message
else -> it is valid! remove any error/validation messages
}
})
}
}
FullNameViewModel:
class FullNameViewModel {
val stateValidationFirstName = MutableLiveData<Validation>() // some validation object
val stateShowEmail = SingleLiveEvent<Any>() // I'm using the hacky single live event here hehe
fun setFullName() {
// do the validations, some process and this can be easily test
// like: stateValidationFirstName = RequiredValidation()
stateShowEmail.call()
}
}
Typically, when an article refers to the view, they don't mean the literal View
class. They mean it in an architectural sense.
Take MVVM. Model-View-ViewModel.
When we talk about Android, the View is the Activity
, Fragment
and View
layout.
So ideally, you should have one ViewModel
for the most logical sets.
If you have an Activity
and a layout, but no Fragment
, then it makes sense to have one ViewModel
to cover the Activity
state, which includes its layout.
If you have an Activity
with its own layout AND a Fragment
, then you might have two ViewModel
s. One for the Activity
state and one for the Fragment
.
I wrote a sample App that might help: https://github.com/DavidEdwards/mvvm-example
Take the example of having a basic Activity
with no layout except for its Fragment
. You would have one ViewModel
in the Fragment
(typically, this is by no means a rule). In this ViewModel
you would have LiveData
representing the state of your View
s. When you want to change the state of a View
, you change the state in the ViewModel
, and the ViewModel
propagates the changes to the View
. I will typically accomplish that by passing the ViewModel
into my layouts using the excellent Android Databindings Library.
You can see an example of that here, here and here. In this example, I am controlling the visibility of a View
by observing the state of the number of players in the game.