Skip to content

Instantly share code, notes, and snippets.

@albertklik
Created July 23, 2025 14:48
Show Gist options
  • Select an option

  • Save albertklik/6fec818dcb2a1258d8a1a6b4689573a4 to your computer and use it in GitHub Desktop.

Select an option

Save albertklik/6fec818dcb2a1258d8a1a6b4689573a4 to your computer and use it in GitHub Desktop.
Update UI State Extention with state builder for enhance MVI architecture
/**
* A contract for a Builder class that can build a final object of type T.
*/
interface Builder<T> {
fun build(): T
}
/**
* A contract for a State class that can provide a Builder for itself.
*/
interface Buildable<B : Builder<out Any>> {
fun toBuilder(): B
}
/**
* Atomically and generically updates a MutableStateFlow.
*
* This function is thread-safe and works for any state class that
* implements the `Buildable` and `Builder` interfaces.
*
* @param T The type of the state, which must be `Buildable`.
* @param B The type of the builder, which must be `Builder<T>`.
* @param block The receiver lambda in which the builder is modified.
*/
fun <T, B> MutableStateFlow<T>.updateWithBuilder(
block: B.() -> Unit
) where T : Buildable<B>, B : Builder<T> {
this.update { currentState ->
// 1. Get the builder from the current state
val builder = currentState.toBuilder()
// 2. Apply the user's modifications to the builder
builder.block()
// 3. Build the new state and return it for the atomic update
builder.build()
}
}
package [...]
import [...]
object ScreenContract {
// The state data class now implements Buildable
data class State(
val loading: Boolean = false,
val success: Boolean = false,
val error: String? = null
): Buildable<State.StateBuilder> {
// The implementation is simple: it just creates and returns an instance of its Builder
override fun toBuilder(): StateBuilder = StateBuilder(this)
// The Builder is a nested class that implements the Builder<State> interface
class StateBuilder(state: State) : Builder<State> {
var loading: Boolean = state.loading
var success: Boolean = state.success
var error: String? = state.error
// The implementation builds and returns a new immutable State object
override fun build(): State = State(loading, success, error)
}
}
sealed interface Event {
data object OnAppear : Event
data object OnLoginClicked : Event
data object OnPoliticaDePrivacidadeClicked : Event
data class OnAccountsLoaded(val emails: List<String>) : Event
}
sealed interface Effect {
data class ShowErrorSnackBar(val message: String) : Effect
}
}
class MyViewModel : ViewModel() {
private val _state = MutableStateFlow(State())
val state: StateFlow<State> = _state
private fun updateUI(block: ScreenContract.State.StateBuilder.() -> Unit) {
_state.updateWithBuilder { it.toBuilder().apply(block).build() }
}
fun fetchData() {
// The call remains clean, but is now generic and 100% thread-safe
updateUI {
loading = true
error = null
}
viewModelScope.launch(Dispatchers.IO) {
// ... fetching data on a background thread ...
val result = "Data received"
// Update the state from any thread with confidence
updateUI {
loading = false
success = true
// error = result // Just an example
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment