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:
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:
_uiStateis private MutableStateFlow for updatesuiStateis public StateFlow for read access- I always need to remember to use
_uiStatewhen 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:
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)
// AfteruiState.value = LoadinguiState.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:
-
Explicit Backing Fields: The
fieldkeyword tells Kotlin to create an actual backing field for the property, not just a computed value. -
Direct Access: When you use
field =, you can access the backing field directly through the property name using.value. -
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.
Related Knowledge
This feature is part of Kotlin’s broader property system improvements. Here are some related concepts:
-
Property Delegation: The
fieldkeyword works alongside property delegates, allowing you to create custom property behaviors. -
Immutable Properties: You can now have immutable properties with mutable backing fields, which is perfect for StateFlow.
-
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:
-
Wrong Syntax: I initially tried
val uiState = MutableStateFlow(Loading)without thefieldkeyword, which gave me compilation errors. -
Wrong Types: I tried using this with regular
Stateinstead ofStateFlow, which doesn’t work. -
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