MO

mobile-state-management-patterns

Evaluate and choose the optimal state management pattern for mobile application complexity.

Install

mkdir -p .claude/skills/mobile-state-management-patterns && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/15220" && unzip -o skill.zip -d .claude/skills/mobile-state-management-patterns && rm skill.zip

Installs to .claude/skills/mobile-state-management-patterns

Activation

This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.

Comparison of mobile state management patterns (MVVM, MVI, TEA, Redux, BLoC, Observable, Compose state, SwiftUI state) and how to choose. Use when designing state flow or evaluating a state library.
198 chars✓ has a “when” trigger
Intermediate

Key capabilities

  • Classify state into UI-ephemeral, Screen, Feature, App, or Persisted tiers
  • Implement unidirectional data flow: Event -> reducer/update -> new State -> View renders
  • Isolate side effects from reducers or update functions
  • Separate transient state from persisted state
  • Ensure a single source of truth for each data fact
  • Test view models independently of the view

How it works

The skill guides the classification of state into tiers and advocates for a unidirectional data flow where events trigger state updates, which then render the view.

Inputs & outputs

You give it
User or system events
You get back
Predictably updated UI

When to use mobile-state-management-patterns

  • Choosing a state management library
  • Architecting mobile data flows
  • Evaluating state architecture complexity

About this skill

Mobile State Management Patterns

Instructions

State management in mobile is the discipline of turning user and system events into UI, predictably. The right pattern depends on state complexity, team size, and framework idioms. This skill names the patterns and shows how they map across stacks.

1. Pattern Families

FamilyShapeGood fit
MVVMViewModel exposes observable stateMost teams, all stacks
MVI / UnidirectionalState = reduce(state, intent)Complex flows, testability
TEA (Elm-like)Model, Msg, update, viewCompose MP, Bloc, Redux Toolkit
Redux / FluxSingle store, actions, reducersCross-platform with many contributors
Observable / ReactiveStreams of stateRN with RxJS/MobX, Combine, Flow
Local / ambientSetters in the viewVery small features only

2. State Scope Tiers

Classify state before choosing a library:

  1. UI-ephemeral -- hover, pressed, scroll position. Lives in the view.
  2. Screen -- loading flag, form draft. Lives in the view model.
  3. Feature -- a multi-screen flow (checkout). Shared view model or feature store.
  4. App -- user session, theme, connectivity. Global store.
  5. Persisted -- survives process death. Backed by disk/keychain.

Using a global store for UI-ephemeral state is the most common smell; so is using local state for app-wide concerns.

3. The Unidirectional Core

Regardless of library, prefer one-way flow:

Event (intent) -> reducer/update -> new State -> View renders

This makes time-travel debug, tests, and undo possible. Two-way bindings are an ergonomic shortcut for small forms, not an architectural default.

4. Cross-Platform Parallels

Counter with loading and error, the simplest non-trivial state:

// Swift (Observation / MV)
@Observable final class CounterVM {
    private(set) var count = 0
    private(set) var isLoading = false
    var error: String?
    func increment() async {
        isLoading = true; defer { isLoading = false }
        do { count = try await api.increment(count) } catch { self.error = "\(error)" }
    }
}
// Kotlin (Compose + StateFlow)
class CounterVM(private val api: Api) : ViewModel() {
    data class State(val count: Int = 0, val loading: Boolean = false, val error: String? = null)
    private val _state = MutableStateFlow(State())
    val state: StateFlow<State> = _state
    fun increment() = viewModelScope.launch {
        _state.update { it.copy(loading = true, error = null) }
        runCatching { api.increment(_state.value.count) }
            .onSuccess { n -> _state.update { it.copy(count = n, loading = false) } }
            .onFailure { e -> _state.update { it.copy(loading = false, error = e.message) } }
    }
}
// Dart (Riverpod Notifier)
class CounterState {
  const CounterState({this.count = 0, this.loading = false, this.error});
  final int count; final bool loading; final String? error;
  CounterState copyWith({int? count, bool? loading, String? error}) =>
      CounterState(count: count ?? this.count, loading: loading ?? this.loading, error: error);
}
class CounterVM extends Notifier<CounterState> {
  @override CounterState build() => const CounterState();
  Future<void> increment() async {
    state = state.copyWith(loading: true, error: null);
    try { state = state.copyWith(count: await ref.read(apiProvider).increment(state.count), loading: false); }
    catch (e) { state = state.copyWith(loading: false, error: e.toString()); }
  }
}
// TypeScript (Zustand)
type CounterState = { count: number; loading: boolean; error?: string; increment: () => Promise<void> };
export const useCounter = create<CounterState>((set, get) => ({
  count: 0, loading: false,
  increment: async () => {
    set({ loading: true, error: undefined });
    try { const n = await api.increment(get().count); set({ count: n, loading: false }); }
    catch (e: any) { set({ loading: false, error: String(e) }); }
  },
}));

5. Choosing a Library

  • Small app, single developer: platform-native (SwiftUI @Observable, Compose remember/rememberSaveable, Flutter ValueNotifier, RN useState).
  • Medium app, testability priority: MVVM with observables (StateFlow, Riverpod, Combine, Zustand).
  • Complex flows, multi-developer: MVI / TEA / Redux Toolkit / BLoC.
  • Cross-platform shared logic (KMP): Coroutines + Flow with reducers in Kotlin, consumed by SwiftUI and Compose.
  • RN with React team muscle: Zustand or Redux Toolkit; avoid context for anything that changes often.

6. Anti-Patterns

  • Multiple sources of truth for the same fact (e.g., auth token in store and in a singleton).
  • Reducers that do network I/O. Side effects belong in commands/middleware/effects.
  • Storing derived data. Compute on read; cache with memoization only if profiled.
  • Sharing BuildContext / UIViewController / Context across state layers.
  • Global stores populated eagerly at app start. Hydrate lazily.

7. State Persistence

Separate transient state from persisted state. Good boundaries:

  • SavedStateHandle on Android restores small UI state across process death.
  • @SceneStorage / @AppStorage on iOS.
  • shared_preferences / secure storage on Flutter for small values.
  • AsyncStorage / MMKV on RN for small values.

Large or sensitive state belongs in a database or keychain, not in user preferences.

8. Testing State

  • Pure reducers/update functions should be 100% covered by unit tests.
  • View models should be tested against fakes of their dependencies.
  • UI tests assert on observable state, not on widget internals.
  • Use time control (virtual clock, TestScheduler, fakeAsync) instead of real delays.

Checklist

  • Every stateful feature classifies its state scope explicitly.
  • Data flow is unidirectional (event -> state -> view) unless a small local form justifies two-way.
  • Side effects are isolated from reducers/update functions.
  • Persisted state is separated from transient state.
  • There is a single source of truth per fact.
  • View models are testable without the view.
  • Library choice matches team size, app complexity, and stack idioms.

When not to use it

  • Using a global store for UI-ephemeral state
  • Using local state for app-wide concerns
  • Reducers that perform network I/O

Limitations

  • Two-way bindings are not recommended as an architectural default
  • Storing derived data is not recommended without memoization if profiled
  • Global stores should not be populated eagerly at app start

How it compares

This approach structures state management with explicit tiers and a one-way flow, unlike ad-hoc state handling that can lead to unpredictable UI behavior.

Compared to similar skills

mobile-state-management-patterns side by side with the closest alternatives in the catalog.

SkillInstallsUpdatedSafetyDifficulty
mobile-state-management-patterns (this skill)03moNo flagsIntermediate
mobile-developer54moNo flagsAdvanced
mobile_engineer02moNo flagsAdvanced
mobile-architect-agent01moNo flagsIntermediate

Try saying

Example prompts that trigger this skill in your AI assistant.

Search skills

Search the agent skills registry