Skip to content

Choosing an Approach

You now know the mechanisms. Choosing between them is mostly about scope and complexity, and it follows a short decision path:

flowchart TB
  q1{"Does more than one widget
need this state?"} -->|no| ss["setState
(ephemeral / local)"]
  q1 -->|yes| q2{"Complex flows,
large app, high testability?"}
  q2 -->|no| prov["Provider / Riverpod
(shared app state)"]
  q2 -->|yes| bloc["BLoC / Cubit
(unidirectional, testable)"]
Which approach to reach for
  • One widget cares? setState. Don’t add a package for a checkbox.
  • Several widgets share it? Provider or Riverpod. Riverpod for new/larger apps.
  • Complex, async-heavy, needs to be testable in isolation? BLoC/Cubit.

There’s no prize for using the heaviest tool. A real app happily mixes all three: setState for local UI bits, a couple of Riverpod providers for shared state, and BLoC only where the complexity truly warrants it.

When two sibling widgets need the same state, move it to their nearest common ancestor and pass it down — lifting state up. This is the first thing to try before reaching for any shared-state package.

SituationWhere the state should live
One widget uses itIn that widget (setState)
Two siblings share itLifted to the common parent
Many widgets across the treeA shared store (Provider/Riverpod)
App-wide, complex, asyncA BLoC/Cubit layer

The single most important habit is classifying state correctly:

  • Ephemeral state — a PageView index, a form field, an animation. Lives in a State. Losing it on rebuild is fine.
  • App state — auth, cart, settings, cached server data. Shared and often persisted. Belongs in a store outside any one widget.

For anything beyond a toy app, layer these responsibilities so they don’t tangle:

  • UI layer — widgets. Renders state, sends inputs. No business logic.
  • State/logic layer — providers, BLoCs, notifiers. Holds and transitions state.
  • Data layer — repositories and services. Talks to APIs, databases, device.

Keeping business logic and data access out of widgets is what makes an app testable and survivable as it grows — regardless of which state package you picked.

What should you try first when two sibling widgets need to share state?
Which state belongs in a shared store rather than a single widget?
What is the benefit of layering an app into UI, state/logic, and data layers?
Can a real app mix setState, Riverpod, and BLoC?