Avoid backing properties for LiveData and StateFlow

Danny Preussler
Jan 12 · 5 min read
https://unsplash.com/photos/OopPIi_A428

If you have ever worked with LiveData you probably have written code similar to this:

class MyViewModel: ViewModel() {   val loading: LiveData<Boolean>
get() = _loading
private val _loading = MutableLiveData<Boolean>()}

This seems nowadays the typical way developers would expose some immutable LiveData, while being able to have a mutable version inside the implementation we would write data into.

Every time I saw, or even had to write, this kind of code something cringed inside me. As I quoted in one of my talks this feeling in our brain is for real:

As developers, we know something is wrong with this code, right? It also feels like we are writing manual getters and setters here.

What’s wrong?

We could start with the prefix we use for the backing field, although we fought hard for a long time to get rid of prefixes, we accept it here! It is even made it into the official coding conventions.

But even if we rename it, it still cringes:

class MyViewModel: ViewModel() {   val loading: LiveData<Boolean>
get() = mutableLoading
private val mutableLoading = MutableLiveData<Boolean>()}

This duplication feels unneeded! Especially if you write something like a ViewModel that exposed many of these, you get lost in reading the code just by all these duplications.

But it's just LiveData?

You might think it’s just a specialty of LiveData and the future of that construct might be a more limited one.

And you would not have this issue with primitives. The language supports this out of the box with a private setter:

var secret: String = "Secret"
private set

But there is a new kid in town: StateFlow needs the same thing! Look at this snippet from the official Jetbrains blog:

class DownloadingModel {   private val _state = MutableStateFlow<DownloadStatus>(DownloadStatus.NOT_REQUESTED)   val state: StateFlow<DownloadStatus> get() = _state

This problem is discussed a lot. Some even suggested a change in the Kotlin language to support this!

Though it feels like we want to use a sledgehammer to crack a nut or shoot with cannons into sparrows or whatever that term in your language!

Isn’t there is something more subtle to solve this?

What is our actual goal?

We want to separate the public API from the actual implementation, right?

Believe it or not, we already have a language construct for this! It’s called Interface!

Although many developers would extract interfaces regularly for classes like Repositories, hardly anyone does for ViewModels.

Why is that? Because there is only one implementation? Maybe as of ViewModels are the last building block before the view, we hardly ever need to mock them in tests?
Maybe! But we just found another good reason to use them.

For the case for ViewModel an abstract class would make it easier as we need to extend Googles ViewModel anyway (you can use interfaces though if you prefer)

abstract class MyViewModel: ViewModel() {
abstract val loading: LiveData<Boolean>
}
class MyViewModelImpl: MyViewModel() {
override val loading = MutableLiveData<Boolean>()
}

From inside the ViewMode has now the type:
override val loading: MutableLiveData<Boolean>

This is called covariant return types, where the implementation returns a more specific type than the original declared. The nice thing now is, that we can directly set values onto our loading state from within our implementation:

class MyViewModelImpl: MyViewModel() {
override val loading = MutableLiveData<Boolean>()
fun doSomeWork() {
// ...
loading.value = true
}
}

Isn’t it leaking details?

You might ask if we aren’t exposing too many implementation details?
If you would use MyViewModelImpl directly (and not the abstract class), you could write into the loading from outside!

The answer is no!

No one except for whoever creating the class should know about it anyway! Normally this would be the layer we do dependency injection or, in the case of ViewModels, the ViewModelFactory. Try to even avoid making it possible to see or create the implementation directly.

Of course, someone can always upcast the interface to the concrete class but could have also upcasted the original loading anyway!

Quoting an older clean coders blog here:

Who would do that?
I dunno. Uh. Someone bad.

Do you have bad people on your team?
No. But. This just doesn’t feel safe.

Well, if this were part of a public API, I’d agree with you. But if this is just code that’s used by our team then…

We don’t need to protect our code against bad actors if those are just our colleagues. Our job is to make sure the API is clear and clean so the usage understandable! Focus on preventing mistakes, not abuse.

Specifics for Google’s ViewModel

ViewModels from Jetpack have some specifics, let’s briefly discuss where to use the abstract class there.

If you use Koin its trivial:

private val viewModel: MyViewModel by viewModel()

in your module you would then declare it like:

viewModel<MyViewModel> { MyViewModelImpl(...) }

When using the delegates from Ktx things are a bit more tricky sometimes (like testing).
It is easy though if you use a custom provider, which could be injected by Dagger for example:

@Inject lateinit var provider: MyViewModelProvider
private val viewModel: MyViewModel by viewModels { provider }

It’s not as obvious if you use the generic factory:

@Inject lateinit var factory: ViewModelProvider.Factory
private val viewModel: MyViewModel by viewModels { factory }

You have to tell Dagger somehow how to do the matching. But should be done just the normal way in your module where you would bind MyViewModel to MyViewModelImpl either manually or with @Bind . It means more Dagger code but less code in the ViewModel, which should be worth it. When it comes to Dagger sometimes more is better if it improves your overall architecture. Focus on improving the code you read and work daily with, not the Dagger setup behind the scenes.

Conclusion

By using interfaces or abstract classes you not only get rid of all the duplications in the implementation but can focus on the functionality as well. Reducing noise is very important for clean and reusable code.

Plus, you create a very clean API for your users, here one from a small Quiz app I was working on, using Flow:

abstract class GameViewModel: ViewModel() {
val nextQuestion: Flow<String>
val nextAnswers: Flow<Answers>
val loadingVisible: Flow<Boolean>
val score: Flow<String>
val gameOver: Flow<Boolean>
}

And if you think back to C programming with header files, this is what we had back then. A clear interface to the outside world, perfect encapsulation.
A forgotten art sometimes…

PS: All the credit goes to Aidan Mcwilliams who put my attention onto this!

Google Developers Experts

Experts on various Google products talking tech.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store