Skip to main content

BaseViewModel<State, Event>

Abstract ViewModel base class providing typed state management, one-time events, and 5 async helpers.

Module: :ui-state

Definition

abstract class BaseViewModel<State, Event>(
initialState: State
) : ViewModel()

Properties

PropertyTypeDescription
uiStateStateFlow<State>Observable UI state
currentStateStateCurrent state snapshot
eventsMutableEventFlow<Event>One-time UI events

State Management

setState

Update state with a reducer function:

setState { copy(isLoading = true, error = null) }

The lambda receiver is the current state — use copy() for data classes.

sendEvent

Emit a one-time UI event:

sendEvent(MyEvent.ShowSnackbar("Item deleted"))
sendEvent(MyEvent.NavigateToDetail(itemId))

Async Helpers

collectResource

Pipes a one-shot suspend -> Resource<T> into a MutableStateFlow<Resource<T>>.

protected fun <T> collectResource(
stateFlow: MutableStateFlow<Resource<T>>,
call: suspend () -> Resource<T>
): Job

Example:

private val _users = MutableStateFlow<Resource<List<User>>>(Resource.Loading())
val users = _users.asStateFlow()

fun loadUsers() = collectResource(_users) { getUsersUseCase() }

collectFlow

Pipes a plain Flow<T> into a MutableStateFlow<Resource<T>>. Each emission is auto-wrapped in Resource.Success.

protected fun <T> collectFlow(
stateFlow: MutableStateFlow<Resource<T>>,
distinctUntilChanged: Boolean = false,
flow: suspend () -> Flow<T>
): Job

Example:

private val _bookmarks = MutableStateFlow<Resource<List<Article>>>(Resource.Loading())
val bookmarks = _bookmarks.asStateFlow()

fun observe() = collectFlow(_bookmarks, distinctUntilChanged = true) {
bookmarkDao.getAll() // Flow<List<Article>>
}

collectFlowResource

Pipes a Flow<Resource<T>> directly into a MutableStateFlow<Resource<T>>.

protected fun <T> collectFlowResource(
stateFlow: MutableStateFlow<Resource<T>>,
distinctUntilChanged: Boolean = false,
flow: suspend () -> Flow<Resource<T>>
): Job

Example:

private val _feed = MutableStateFlow<Resource<List<FeedItem>>>(Resource.Loading())
val feed = _feed.asStateFlow()

fun observe() = collectFlowResource(_feed) {
liveFeedUseCase() // Flow<Resource<List<FeedItem>>>
}

execute

One-shot suspend call with state reducers for full control:

protected fun <T> execute(
call: suspend () -> Resource<T>,
onLoading: (State.() -> State)? = null,
onSuccess: State.(T) -> State,
onError: (State.(AppException) -> State)? = null,
errorEvent: ((AppException) -> Event)? = null
): Job

Parameters:

ParameterTypeDescription
callsuspend () -> Resource<T>The async operation
onLoading(State.() -> State)?State reducer for loading state
onSuccessState.(T) -> StateState reducer for success (required)
onError(State.(AppException) -> State)?State reducer for error
errorEvent((AppException) -> Event)?Emit an event on error

Example:

fun loadProducts() = execute(
call = { getProductsUseCase() },
onLoading = { copy(isLoading = true) },
onSuccess = { copy(products = it, isLoading = false) },
onError = { copy(error = it.message, isLoading = false) },
errorEvent = { ProductEvent.ShowError(it.message ?: "Failed") }
)

collect

Reactive stream with state reducers:

protected fun <T> collect(
flow: () -> Flow<Resource<T>>,
distinctUntilChanged: Boolean = false,
onLoading: (State.() -> State)? = null,
onSuccess: State.(T) -> State,
onError: (State.(AppException) -> State)? = null,
errorEvent: ((AppException) -> Event)? = null
): Job

Example:

fun observeBookmarks() = collect(
flow = { observeBookmarksUseCase() },
distinctUntilChanged = true,
onSuccess = { copy(bookmarks = it, isLoading = false) }
)

Decision Matrix

NeedUse
Simplest possible — one Resource drives the UIcollectResource
Observe a Room/DataStore FlowcollectFlow
Observe a Flow that already emits ResourcecollectFlowResource
One-shot call, need to update multiple state fieldsexecute
Stream, need to update multiple state fieldscollect