State Management Decisions

Choose state management by state scope, not by package popularity.

First Split

State typeExamplesStart with
Ephemeral widget stateselected tab, text field focus, expanded panelStatefulWidget plus setState
Screen UI stateloading/error/data for one screenView model, ValueNotifier, or package-backed notifier
App statelogin session, theme, cart, synced data, unread countsApp-level state mechanism and repository backing
Durable datalocal database rows, preferences, cached server dataRepository plus storage package

Decision Ladder

  1. Use setState for local visual state.
  2. Use ValueNotifier or ChangeNotifier for small shared state.
  3. Add Provider when inherited access and dependency wiring become annoying.
  4. Add Riverpod when you want explicit providers, testable dependency overrides, and less BuildContext coupling.
  5. Add Bloc/Cubit when event/state discipline, logs, and predictable flows matter more than boilerplate cost.

Default For This Lab

Do not set a universal package default. For a small solo-dev Flutter starter, start with local state plus simple view models. Add Riverpod only when dependency overrides, async state, or cross-feature shared state become real. Use Bloc only when the app benefits from explicit event/state machinery.

Migration Rule

State can move outward:

setState -> view model -> repository-backed app state -> sync-aware state

If state never crosses a widget boundary, do not promote it.

Gotcha: Global state is easy to add and hard to reason about. Persisted state is even harder because migrations, restore, and privacy enter the design.