Skip to content

How to eliminate _uiState dual property pattern in Kotlin ViewModels

Purpose

This post demonstrates how to eliminate the annoying dual property pattern in Kotlin ViewModels. When I first heard about Kotlin 2.3’s new Explicit Backing Fields feature, I was skeptical. But after trying it, I got excited about how much boilerplate it eliminates.

The Problem

When I write Android ViewModels, I always get frustrated with the dual property pattern. Here’s what I used to write:

MyViewModel.kt
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(Loading)
val uiState: StateFlow<UiState> get() = _uiState
fun loadData() {
viewModelScope.launch {
try {
_uiState.value = Loading // Must use _uiState
val data = repository.getData()
_uiState.value = Success(data) // Easy to forget the underscore
} catch (e: Exception) {
_uiState.value = Error(e) // More boilerplate
}
}
}
}

I can explain the key parts:

  • _uiState is private MutableStateFlow for updates
  • uiState is public StateFlow for read access
  • I always need to remember to use _uiState when updating values

But when I have multiple state properties, this pattern becomes tedious. I tried to simplify it, but I kept getting compilation errors.

Environment

  • Android Studio
  • Kotlin 2.3.0
  • Android Gradle Plugin 8.2
  • Kotlin Coroutines 1.8.0

How to solve it?

I tried searching for solutions. Most posts showed the same pattern I was already using. Then I found a Reddit discussion about Kotlin 2.3.

I tried implementing the new Explicit Backing Fields syntax:

MyViewModel.kt
class MyViewModel : ViewModel() {
val uiState: StateFlow<UiState> field = MutableStateFlow(Loading)
fun loadData() {
viewModelScope.launch {
try {
uiState.value = Loading // Direct access - no underscore!
val data = repository.getData()
uiState.value = Success(data) // Clean and simple
} catch (e: Exception) {
uiState.value = Error(e) // No more boilerplate
}
}
}
}

I can see that the key change is on line 2:

  • Before: private val _uiState = MutableStateFlow<UiState>(Loading)
  • After: val uiState: StateFlow<UiState> field = MutableStateFlow(Loading)

The field keyword creates an explicit backing field that can be accessed directly.

Now test again:

// Before
_uiState.value = Loading
_uiState.value = Success(data)
_uiState.value = Error(e)
// After
uiState.value = Loading
uiState.value = Success(data)
uiState.value = Error(e)

You can see that I succeeded to eliminate the dual property pattern completely.

Why this works

I think the key reasons why this solution works are:

  1. Explicit Backing Fields: The field keyword tells Kotlin to create an actual backing field for the property, not just a computed value.

  2. Direct Access: When you use field =, you can access the backing field directly through the property name using .value.

  3. Type Safety: The property still has the correct type (StateFlow<UiState>), so you get full type safety and smart-cast support.

The Kotlin 2.3 Documentation

Here’s what the official Kotlin 2.3 documentation says about this feature:

Explicit backing fields allow you to declare a property with an explicit backing field, which can be accessed directly through the property name. This is useful for cases where you need both read access and direct field access.

This feature is part of Kotlin’s broader property system improvements. Here are some related concepts:

  1. Property Delegation: The field keyword works alongside property delegates, allowing you to create custom property behaviors.

  2. Immutable Properties: You can now have immutable properties with mutable backing fields, which is perfect for StateFlow.

  3. Compilation Performance: Explicit backing fields can sometimes improve compilation performance since the compiler doesn’t need to generate accessors.

Common Mistakes

I made some mistakes while learning this feature:

  1. Wrong Syntax: I initially tried val uiState = MutableStateFlow(Loading) without the field keyword, which gave me compilation errors.

  2. Wrong Types: I tried using this with regular State instead of StateFlow, which doesn’t work.

  3. Dependency Issues: I forgot to update my Kotlin version to 2.3.0, so the feature wasn’t available.

Summary

In this post, I showed how to eliminate the _uiState dual property pattern in Kotlin ViewModels using the Explicit Backing Fields feature. The key point is that Kotlin 2.3 allows you to write val uiState: StateFlow<UiState> field = MutableStateFlow(Loading) instead of the old dual property pattern, making your code cleaner and less error-prone.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments