Android MVVM — good practices.

Jakub Neukirch
4 min readNov 27, 2019

This post is not tutorial about implementing MVVM architectural pattern —it is list of good practices and misconceptions of MVVM commonly committed by android developers.

ViewModel vs AndroidViewModel

tl;dr Do not use AndroidViewModel, it ruins whole testability which MVVM provides in the first place.

/* For simplification of example, this part assumes we don’t use UseCase layer (which should be used) */

AndroidViewModel is what its name says — it is plain ViewModel with addition of a little bit of Android — in constructor We need to pass Application instance and we can access it from within ViewModel. Thats all from differences. Just that, one simple thing to prevent you from mocking your ViewModel easily. It was said not a single time, no matter which architectural pattern you use, do not use android dependencies in Bussiness Logic Layer.

Ok, so what alternative do We have?- Creating wrapper classes which will be responsible for things you used, for either Application or any other android dependency. This way you will have exposed some specific functionalities which are actually being used and only these will be mocked in your Unit Tests— imagine mocking Application.getSharedPreferences() or all SharedPreferences.get/put methods — too much work.

Lets assume you used Application for getting some data from SharedPreferences — what should be done is creating some Settings class which will expose specific SharedPreferences fields, and SharedPreferences instance should be one of constructor parameters of Settings class.

private const KEY_DARK_MODE = "dark_mode"class Settings(private val _sharedPreferences: SharedPreferences) {
var isDarkMode: Boolean
set (value) {
_sharedPreferences.edit {
putBoolean(KEY_DARK_MODE, value)
}
}
get() = _sharedPreferences.getBoolean(KEY_DARK_MODE)
}

What is more, this solution allows you setting and getting data from your prefs with simple kotlin syntax — _settings.isDarkMode = true

ViewModel instantiation

tl;dr Do not instantiate ViewModel by constructor directly in your View, instead use ViewModelProvider, and ViewModelFactory, otherwise it will be bound to lifecycle and instantiated each time onCreate is being called.

Each ViewModel needs to have ViewModelFactory, which will be responsible for instantiating in provider, here is example:

class LoginViewModelFactory(private val _settings: Settings): ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return LoginViewModel(_settings)
}
}

ViewModelProvider

And to fetch it you use ViewModelProvider , first parameter of ViewModelProvider constructor is ViewModelStoreOwner so in Activities and Fragments we can just pass this , second parameter is ViewModelProvider.Factory instance. On instantiated ViewModelProvider you can invoke get to acquire ViewModel of specified Class type as parameter,

viewModel = ViewModelProvider(this, loginViewModelFactory)
.get(LoginViewModel::class.java)

ViewModelStoreOwner

ViewModelStoreOwner takes care of proper instantiation of ViewModelStore to keep it separated from configuration changes and app lifecycle, so data is being kept, and ViewModels are not reinstantiated. ViewModelStore (and ViewModels) are being cleared only in onDestroy when it is not caused by configuration changes (for example orientation change) - this is the magic of MVVM lifecycle independence, below is code from ComponentActivity:

getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
}
}
})
;

Ok, so we have ViewModelStoreOwner in ViewModelProvider, what’s next?
Long story short, get method takes ViewModel instance from store if already instantiated, if it is not it is being instantiated and put into ViewModelStore so when we request for ViewModel of this type after configuration change we will get the same instance, so already loaded data will be kept and won’t be fetched again.

/* There are ways to make it more abstract but it depends on which Dependency Injection library you use, shortly: THIS is tutorial making it with dagger2, and in koin you just provide it with method viewModel inside your module — instantiating by constructor, all factories and providing are done by koin. */

No UI in ViewModel

tl;dr Do not define texts, images etc. in ViewModel — define it in View depending on current LiveData value.

It somehow relates to AndroidViewModel part — No android dependencies in bussiness logic. Acquiring strings or drawables in ViewModel requires context, which shouldn’t be included in your ViewModel as it is Android Dependency. Okay it can be done with some wrapper, but in this case if you set some String or Drawable as LiveData value it ruins testability.

Lets say you have some ImageView and depending on the state it should display different image: for failure — red “X”, for loading — circular progress, for success — green ✓. If you fetched Drawable from some wrapper you would have android dependency in ViewModel (Drawable itself) and it would be hard to test, you would need to create Drawable instance, and it is abstract class, so you would need to find some subclass for example GradientDrawable, instantiate it, and then compare returned instance with your expected instance, but how would you differ failed, loading and success drawable?
Maybe this is not so accurate with strings but there is another reason - View must be separated from bussiness logic. Bussiness logic should tell you what is the state, and the view should react adequately to current state and this reaction should be defined in View layer.

So what is the solution? — Enum classes. It will be much better to just create enum with three states FAILED, IN_PROGRESS, SUCCESS, and then easily set your image in observer. This way you have strictly separated View and Logic layer.

viewModel.currentState.observe(this, Observer { state ->
val drawableId = when(state) {
DataState.FAILED -> R.drawable.failedIcon
DataState.IN_PROGRESS -> R.drawable.circularProgress
DataState.SUCCESS -> R.drawable.successIcon
}
stateImageView.setDrawable(drawableId)
}

End

--

--