Chanzmao ʕ•ᴥ•ʔ Bear Blog

UI Actions Should Not Mutate UiState

Let UiState Be Derived From Its Inputs

UI = f(State) is the foundation of declarative UI.

But how should UI actions change that state?

My rule is simple:

UI actions should not directly mutate public UiState.

Instead, they update private inputs in the ViewModel. UiState is derived from those inputs.

UI Action
    ↓
Private Flow
    ↓
UiState
    ↓
UI

For example:

private val _query = MutableStateFlow("")



val uiState = combine(
        _query,
        repository.items
    ) { query, items ->
        UiState(
            query = query,
            items = items
        )
    }

fun onQueryChanged(query: String) {
    _query.value = query
}

Compare this with directly modifying UiState:

fun onQueryChanged(query: String) {
    _uiState.update {
        it.copy(query = query)
    }
}

The second approach makes UI actions responsible for maintaining screen state.

The first approach makes the responsibility clearer:

UI Action → change input
                  ↓
            derive UiState
                  ↓
                render

The UI action says what happened.

The Flow graph defines what the resulting state should be.

This is the key distinction:

UiState = f(Inputs)

rather than:

UI Action → mutate UiState

The goal is not to eliminate imperative code.

It is to keep imperative mutation at the boundary and let the state itself be derived.

UI actions are triggers for changing inputs, not commands for mutating UiState.

References

#Android #Flow #Jetpack Compose #Kotlin