home / skills / hoangnguyen0403 / agent-skills-standard / state

state skill

/skills/android/state

This skill guides Android state management using ViewModel, StateFlow, and LCE patterns to ensure robust, testable UI state handling.

npx playbooks add skill hoangnguyen0403/agent-skills-standard --skill state

Review the files below or copy the command above to add this skill to your agents.

Files (2)
SKILL.md
1.1 KB
---
name: Android State Management
description: Standards for ViewModel, StateFlow, and UI State Patterns
metadata:
  labels: [android, state, viewmodel, flow]
  triggers:
    files: ['**/*ViewModel.kt', '**/*UiState.kt']
    keywords: [viewmodel, stateflow, livedata, uistate]
---

# Android State Management

## **Priority: P0**

## Implementation Guidelines

### ViewModel Pattern

- **Exposure**: Expose ONE `StateFlow<UiState>` via `.asStateFlow()`.
- **Scope**: Use `viewModelScope` for all coroutines.
- **Initialization**: Trigger initial load in `init` or `LaunchedEffect` (once).

### UI State (LCE)

- **Type**: sealed interface `UiState` (Loading, Content, Error).
- **Immutability**: Data classes inside should be `@Immutable`.

### Flow Lifecycle

- **Collection**: Use `collectAsStateWithLifecycle()` in Compose.
- **Hot Flows**: Use `SharingStarted.WhileSubscribed(5000)` for shared resources.

## Anti-Patterns

- **LiveData**: `**No LiveData**: Use StateFlow.`
- **Mutable State**: `**No Mutable Public**: Expose read-only Flow.`
- **Context**: `**No Context in VM**: Memory Leak Risk.`

## References

- [Templates](references/implementation.md)

Overview

This skill documents practical standards for Android state management using ViewModel, StateFlow, and UI state patterns. It defines patterns to keep UI state predictable, lifecycle-safe, and testable across Compose and traditional UI layers. The guidance focuses on a single read-only StateFlow, sealed UI state types, and coroutine scope discipline.

How this skill works

The skill prescribes exposing one StateFlow<UiState> from each ViewModel, backed by a private MutableStateFlow and published with asStateFlow() to prevent external mutation. It mandates using viewModelScope for all coroutines and initializing load logic once (init or a Compose LaunchedEffect). In Compose, it recommends collectAsStateWithLifecycle() and using SharingStarted.WhileSubscribed(5000) for shared hot flows to balance resource use and responsiveness.

When to use it

  • Building new Android screens with Jetpack Compose or migrating existing ones to StateFlow.
  • When you need predictable, testable UI state and lifecycle-aware collection.
  • When avoiding memory leaks from Context misuse inside ViewModels.
  • When replacing LiveData-based patterns for a unifying reactive approach.
  • When sharing data streams across multiple collectors while avoiding unnecessary work.

Best practices

  • Expose a single, read-only StateFlow via .asStateFlow() and keep MutableStateFlow private.
  • Model UI state as a sealed UiState (Loading, Content, Error) and use immutable data classes.
  • Run all coroutines in viewModelScope and trigger initial loads in init or a single LaunchedEffect.
  • Use collectAsStateWithLifecycle() in Compose to respect lifecycle states automatically.
  • For shared hot flows, prefer SharingStarted.WhileSubscribed(5000) to stop work when unused.
  • Avoid holding Android Context or Views in ViewModel to prevent leaks.

Example use cases

  • A Compose screen showing loading, content, and error states sourced from a repository.
  • Migrating a LiveData-based ViewModel to StateFlow for more consistent testing.
  • Sharing a user session stream across multiple fragments without continuous work when no UI is observing.
  • Initializing a paged feed in ViewModel init and exposing the UiState for upstream UI rendering.
  • Unit testing ViewModel business logic by asserting emitted UiState values from the StateFlow.

FAQ

Why one StateFlow instead of multiple flows?

A single UiState flow centralizes UI representation, reduces race conditions, and simplifies Compose collection and testing.

Can I use LiveData instead?

LiveData is discouraged here; StateFlow provides a consistent Kotlin coroutine API and better interoperability with Compose and structured concurrency.