View Model Doesn’t Have To Depend on ViewModel

Serge Shustoff
Wrike TechClub
Published in
4 min readJun 21, 2021

Google’s best practices say to use ViewModel as a base class for our View Models. But what if we didn’t? ViewModel is a great tool for making something survive configuration changes, but can we use it without extending the ViewModel class directly?

Why would anyone want to avoid using ViewModel directly? There are several reasons:

  • ViewModel is an abstract class. No one likes extending abstract classes without a good reason.
  • ViewModel is platform-dependent. What if we want our VM layer to be used with Kotlin Multiplatform in the future? (Or right now!)
  • ViewModel isn’t testable sometimes, since CoroutineScope is provided inside and can’t be easily replaced in tests.

What does ViewModel do for us and what are the drawbacks?

To improve something we first need to understand what it does right and wrong.

Surviving configuration changes

ViewModel can survive through orientation changes, and survives a bit more when a fragment with this VM goes to the back stack. That’s what we love about ViewModel, right?

Drawbacks:

We have to extend the abstract class ViewModel. Even if it’s not a View Model from MVVM, to persist it through orientation changes we have to extend ViewModel. Presenter? Extend ViewModel. Utility classes that have nothing to do with MVVM? Extend ViewModel anyway.

Closing resources

ViewModel allows us to clear resources used in VM when said VM is no longer needed. Basically, when the screen (fragment/activity) with this VM is closed for good.

Drawbacks:

ViewModel should pass through ViewModelProvider.get() for the ‘onCleared()’ method to work properly. It doesn’t look like a big deal but combined with DI and some common approaches, like using generic ViewModelFactory, it might hinder us. It’s highly unlikely but still worth noting.

CoroutineScope

ViewModel provides us with CoroutineScope (if we use the ktx library), which easily follows from previous features. We can also do it ourselves without the help of ktx if we want to.

Drawbacks:

  • SRP violation and bad testability, since ViewModel decides how to create and close CoroutineScope. It would be better if CoroutineScope was provided outside of ViewModel without View Model knowing how to create and destroy it.
  • CoroutineScope is available outside of ViewModel. If we don’t hide View Model behind an interface, our View might do something in CoroutineScope and leak a bit. This is an unlikely scenario, but the possibility is still there.

SavedStateHandle

SavedStateHandle allows us to save data to bundle in case of process death.

What can we do to avoid all these drawbacks?

Not extending ViewModel

First, let’s think what the ideal API would be for getting something persisted through configuration changes without it being a ViewModel, like this:

How do we achieve something like this? We need a ViewModel implementation that would persist things for us:

That’s it! Only a few lines of code and we can make anything survive configuration changes:

Closing resources

But what if we have some resources in our persisted objects? Well, we need a way to free them without adding abstract class or interface. Instead of implementing something, we can actively register for a callback that tells our persisted object that its owner (screen/fragment/activity) is done for and it’s time to release resources:

The name may not be perfect, but I haven’t found a better one so far.

Now our persisted object can manage its resources and state it clearly in a constructor signature. We can’t create it by mistake without providing support for clearing resources:

CoroutineScope

This is the easiest one. We need to provide CoroutineScope from outside of our persisted object, for testability and such things:

We can just use an existing ViewModel scope like this:

Or, we could create our CoroutineScope to have more control over it. It’s possible but outside of this article’s scope.

SavedStateHandle

That’s the most difficult part. We need to be able to save data during process death without depending directly on an Android library. And the API should be easy to use.

If we want to use delegates here, like:

Then our SavedStateHelper interface will have only one method:

Now we need to make an implementation for SavedStateHelper:

Note “${thisRef.javaClass.name}__$key.” Keys should be tied to the user’s class because we use the same SavedStateHandle for each persisted object on a fragment, so we need to make sure that keys from different sources don’t mix.

This implementation will support the same types that SavedStateHandle does, but it’s better not to use Bundle or Parcelable in View Model if we want to separate our code from the Android framework.

Conclusion

Now we have a simple API for persisting our View Models, Presenters, or whatever we want without directly relying on a third-party library. Plus, we have a buffer between our code and Google’s library.

Finally, our View Model can be created like this:

It’s a common case of Constructor Injection. We can use a DI framework to make it easier, but that’s a topic for another article.

By the way, our awesome team is looking for an awesome Middle/Senior Android Developer in Prague. If you’re interested, feel free to apply to our position.

--

--