wnb-viewmodel-udflisted
Install: claude install-skill wenubey/claude-android-skills
# Android ViewModel — UDF contract
This skill encodes a single ViewModel shape used across a Kotlin + Jetpack Compose codebase. Do not invent variants. If a rule blocks the task, stop and surface the conflict — don't silently break the contract.
## Non-negotiables
1. **One state class per ViewModel.** Named `XxxState`, immutable `data class`, mutated via `copy()`. Everything the UI renders is a field on this class — loading, error, list contents, dialog visibility, form input.
2. **One `StateFlow<XxxState>`.** Backing `MutableStateFlow` is private (`_state`); public is `val state: StateFlow<XxxState> = _state.asStateFlow()`.
3. **One `onAction(action: XxxAction)` entry point.** All UI events flow through it. `XxxAction` is a sealed interface (or sealed class) declared in the same file or feature package. No public `onXxx()` per-event methods.
4. **No `mutableStateOf` inside a ViewModel.** Compose-side only.
5. **No string keys for navigation.** Use type-safe route classes.
6. **IO always goes through an injected `DispatcherProvider`.** Never `Dispatchers.IO` directly — it can't be swapped in tests.
7. **Repositories return `Result<T>` or `Flow<T>`.**
- `Result<T>` → `.onSuccess { _state.update { ... } }.onFailure { Timber.e(it, "..."); _state.update { it.copy(error = ...) } }`
- `Flow<T>` → `.catch { Timber.e(it, "..."); _state.update { ... } }.collect { ... }`
8. **One-shot events** (navigation, snackbar, toast) go through `MutableSharedFlow<T>(extraBufferCapacity =