android-viewmodel
Best practices for implementing Android ViewModels using Kotlin 2.3+ Explicit Backing Fields, StateFlow for UI state, and SharedFlow for one-off events.
Works with
---
name: android-viewmodel
description: Best practices for implementing Android ViewModels using Kotlin 2.3+ Explicit Backing Fields, StateFlow for UI state, and SharedFlow for one-off events.
license: Apache-2.0
---
# Android ViewModel & State Management
## Instructions
Use `ViewModel` to own UI state and business logic. It should survive configuration changes and expose immutable APIs.
> **Kotlin 2.3+**
>
> Prefer **Explicit Backing Fields** instead of the traditional `_state`/`state` backing property pattern.
>
> - Kotlin 2.3: Experimental (`-Xexplicit-backing-fields`)
> - Kotlin 2.4+: Stable (recommended)
>
> Avoid creating duplicate backing properties unless targeting older Kotlin versions.
---
## 1. UI State (StateFlow)
### Purpose
Represents the persistent UI state.
Examples:
- Loading
- Success(data)
- Error(message)
- Empty
### Declaration
Use an explicit backing field.
```kotlin
val uiState: StateFlow<UiState>
field = MutableStateFlow(UiState.Loading)
```
The backing field is mutable (`MutableStateFlow`) while consumers only see `StateFlow`.
### Updating State
Prefer `update { }` for atomic updates.
```kotlin
_uiState.update { old ->
old.copy(isLoading = false)
}
```
With explicit backing fields:
```kotlin
uiState.update {
it.copy(isLoading = false)
}
```
You may also assign directly when appropriate.
```kotlin
uiState.value = UiState.Success(data)
```
---
## 2. One-Off Events (SharedFlow)
### Purpose
Use for transient events that should not survive recomposition or configuration changes.
Examples:
- Navigate
- Show Snackbar
- Show Toast
- Open Dialog
- Request Permissions
### Declaration
```kotlin
val uiEvent: SharedFlow<UiEvent>
field = MutableSharedFlow(
replay = 0,
extraBufferCapacity = 1
)
```
`replay = 0` ensures events are **not replayed** after recreation.
Using `extraBufferCapacity = 1` is recommended for UI events sent with `tryEmit()`.
### Sending Events
Suspend:
```kotlin
uiEvent.emit(UiEvent.NavigateBack)
```
Non-suspending:
```kotlin
uiEvent.tryEmit(UiEvent.ShowSnackbar("Saved"))
```
---
## 3. Collecting in Compose
### StateFlow
Always collect lifecycle-aware.
```kotlin
val state by viewModel.uiState.collectAsStateWithLifecycle()
```
### SharedFlow
Collect inside a single `LaunchedEffect`.
```kotlin
LaunchedEffect(Unit) {
viewModel.uiEvent.collect { event ->
when (event) {
is UiEvent.NavigateBack -> { /* ... */ }
is UiEvent.ShowSnackbar -> { /* ... */ }
}
}
}
```
Do **not** collect `SharedFlow` with `collectAsState()`.
---
## 4. Collecting in XML/View System
Use lifecycle-aware collection.
```kotlin
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
render(it)
}
}
}
```
Likewise for events.
```kotlin
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiEvent.collect(::handleEvent)
}
}
```
---
## 5. Coroutine Scope
Launch all ViewModel work in `viewModelScope`.
```kotlin
viewModelScope.launch {
repository.refresh()
}
```
Business logic should preferably be delegated to UseCases or Repositories.
---
## 6. State Design
Represent the entire screen with a single immutable state object.
```kotlin
data class UiState(
val isLoading: Boolean = false,
val items: List<Item> = emptyList(),
val error: String? = null
)
```
Avoid exposing multiple unrelated `StateFlow`s for one screen unless they truly have different lifecycles.
---
## 7. Best Practices
Expose immutable APIs (`StateFlow`, `SharedFlow`)
Prefer Explicit Backing Fields (Kotlin 2.4+)
Use immutable UI state
Use `update {}` when modifying state
Keep one-off events in `SharedFlow`
Keep business logic out of Composables
Use `collectAsStateWithLifecycle()`
Use `repeatOnLifecycle()` in Views
Launch work in `viewModelScope`
Do not expose `MutableStateFlow`
Do not use `StateFlow` for navigation events
Do not use `SharedFlow` for persistent screen state
Do not keep duplicate `_state` properties when Explicit Backing Fields are availableMore Mobile skills
animation-vocabulary
emilkowalski/skills
Reverse-lookup glossary that turns a vague description of a web animation or motion effect into its exact term ("the bouncy thing when a popover opens" → Pop in; "the iOS rubber-band scroll" → Rubber-banding). Use when the user asks "what's it called when…", or describes a motion effect without knowing its name and wants the right word to prompt an AI or designer with. For naming an effect, not designing or building one.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
cross-border-ecommerce
nexscope-ai/ecommerce-skills
Cross-border e-commerce expansion advisor. Scores target markets on 8 weighted dimensions (market size, ecommerce penetration, competition, regulatory complexity, logistics infrastructure, payment ecosystem, cultural distance, IP protection), compares 5 fulfillment models with cost and transit data, provides country-by-country tax/duty compliance guides (EU VAT/IOSS, UK VAT, US sales tax, CA GST, AU GST, JP consumption tax), maps local payment preferences by market, and builds a phased expansion roadmap. No API key required.

