Search code examples
androidmvvmandroid-architecture-componentsandroid-mvvm

Reusing ViewModels - State for each screens that will use it


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()
   }
}

Solution

  • 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 ViewModels. 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 Views. 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.