ViewModels in Clean Architecture-Dos and Don’ts-Part 2

BHAVNA THACKER
3 min readApr 15, 2023

--

In this blog, we are going to focus on the best practices when the ViewModel deals with UI State and passes on the updates in the UI State to the UI .

You would often come across the UI state defined like below:

data class NewsUiState(
val isSignedIn: Boolean = false,
val isPremium: Boolean = false,
val newsItems: List<NewsItemUiState> = listOf(),
val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)

Source: https://developer.android.com/topic/architecture/ui-layer

1. Why all properties of a UI state are recommended to be val and not var?

  • By declaring properties as val instead of var, you are achieving the Limiting Mutability feature of Kotlin which helps build Scalable, Testable and Maintainable Apps (more on this in later part of blog).
  • As a Bonus, you help Jetpack Compose (if your UI is in compose) to skip a specific composable during Recomposition thereby optimising the performance. Not clear? Let me explain with an example.
@Composable
fun NewsItemRow(state: NewsItemUiState, modifier: Modifier = Modifier) {
var selected by remember { mutableStateOf(false) }
Row(modifier) {
NewsItemDetails(state)
ItemButton(selected, onClick = { selected = !selected })
}
}

Now, when you click ItemButton, compose will trigger recomposition of NewsItemRow function. When it reaches NewsItemDetails function, it will skip recomposing it as it knows that its parameter (state: NewsItemUiState) is not changed. But what if, any of properties of NewsItemUiState are var instead of val? In that case, compose can’t skip recomposition of NewsItemDetails composable as NewsItemUiState class is considered as “Unstable” now because Properties of NewsItemUiState class could have changed without compose knowing about it.

Wondering what is Stable and Unstable here? — Jetpack Compose Stability explained in detail here.

2. Official docs mention: UI state is an immutable snapshot of the details needed for the UI to render.

When I made above statement — One obvious question asked by the audience in a conference talk last year was:

If UI state is immutable, then how do you update the UI when the state change?

Here in order to achieve this, we do three things in ViewModel and thereby follow Limiting Mutability feature of Kotlin.

  1. Declare properties in UI state as val instead of var
  2. Keep Mutable and Read Only collections Separate
  3. Use copy in data classes

So, our ViewModel looks like below:

class NewsViewModel(
private val getNewsUseCase: GetNewsUseCase,
...
) : ViewModel() {

//2. Keep Mutable(_uiState) and Read Only (uiState) state Separate
private val _uiState = MutableStateFlow(NewsUiState())
val uiState = _uiState.asStateFlow()

init {
viewModelScope.launch {
//3. Use copy in data classes
// TODO get the news & map to newsItems
_uiState.update { it.copy(newsItems = newsItems) }
}
}

}

It is advisable to update the UI state using update and copy functions.

If you check the definition of update function, it looks like below:

So, it helps you update the state atomically and hence recommended in Multithreaded environment to ensure data consistency.

copy function creates a new instance and copies all properties from previous instance except the one that you override.

The only scenario, where you can skip using copy is when you don’t care about previous state like when you are resetting the state for some reason.

For e.g.:

@Composable
fun onLogOut() {
_loginUiState.update { LoginUiState() }
}

That’s it folks! We will continue the ViewModels with Clean Architecture discussion in the next blog as I would like to limit this part to UI state.

Till then, Happy Coding!

References:

https://medium.com/androiddevelopers/jetpack-compose-stability-explained-79c10db270c8

--

--

BHAVNA THACKER

Android GDE. Youtuber — LearnAndroid. Senior Android Engineer @MEGA. FPE @raywenderlich.