Irbisa · Cheatsheet

State with Provider

Provider package, ChangeNotifier, Consumer, context.watch/read/select

Middle Developer · 21 questions

  1. 01

    What are ChangeNotifier and ChangeNotifierProvider? Build a counter with them.

    ChangeNotifier is a class that holds state and calls notifyListeners() after each change; ChangeNotifierProvider creates it, puts it into the widget tree and disposes it when it leaves.

    class Counter extends ChangeNotifier {  int _value = 0;  int get value => _value;   void increment() {    _value++;…
  2. 02

    Tapping +1 never changes the number on screen, and in debug the tap throws. What is wrong with this widget?

    class CounterView extends StatelessWidget {  const CounterView({super.key});   @override  Widget build(BuildContext context) {    final counter = context.read<Counter>();    return Column(children: [      Text('${counter.value}'),      ElevatedButton(        onPressed: () => context.watch<Counter>().increment(),        child: const Text('+1'),      ),    ]);  }}

    The two calls are swapped: read in build takes the value once without subscribing, and watch in onPressed is a listen call outside the build phase, so it asserts before increment() ever runs.

  3. 03

    What is MultiProvider, and what does the lazy flag on a provider control?

    MultiProvider is a flat way to write nested providers (the same tree, just readable).

    void main() {  runApp(    MultiProvider(      providers: [        Provider<ApiClient>(create: (_) => ApiClient()),        ChangeNotifierProvider(…
  4. 04

    The switch below never moves when tapped, although darkMode does change. Why?

    class Settings {  bool darkMode = false;}// above the appProvider<Settings>(create: (_) => Settings(), child: const App());// in the settings screen@overrideWidget build(BuildContext context) {  final settings = context.watch<Settings>();  return SwitchListTile(    title: const Text('Dark mode'),    value: settings.darkMode,    onChanged: (v) => settings.darkMode = v,  );}

    Provider<T> only holds a value and never listens to it.

  5. 05

    What is the difference between context.watch, context.read, context.select and Consumer?

    watch returns the value and rebuilds the whole widget on every notification, read returns it once with no subscription, select rebuilds only when one derived value changes, and Consumer rebuilds only its builder.

    class CartBadge extends StatelessWidget {  const CartBadge({super.key});   @override  Widget build(BuildContext context) {    // rebuilds only when count changes, not when prices change…
  6. 06

    What is the difference between Provider, ChangeNotifierProvider, FutureProvider and StreamProvider?

    They differ in what makes dependents rebuild: Provider exposes a value and never listens, ChangeNotifierProvider rebuilds on notifyListeners(), and FutureProvider and StreamProvider expose the latest result of a Future or a Stream.

    MultiProvider(  providers: [    Provider<ApiClient>(create: (_) => ApiClient()),    ChangeNotifierProvider(create: (_) => CartNotifier()),    FutureProvider<RemoteConfig?>(      create: (context) => context.read<ApiClient>().fetchConfig(),…
  7. 07

    What is the difference between ChangeNotifierProvider(create: ...) and ChangeNotifierProvider.value, and who disposes the object?

    create builds the object and makes the provider its owner: built once, disposed when the provider leaves the tree.

    // right: the provider owns it, creates once, disposes on unmountChangeNotifierProvider(  create: (_) => CartNotifier(),  child: const ShopApp(),)…
  8. 08

    What do Selector and context.select do, and how do they cut rebuilds compared with Consumer?

    Both subscribe to one value derived from the provider and rebuild only when that value changes, while Consumer and watch rebuild on every notifyListeners(), whatever changed.

    // rebuilds only when the name changesfinal name = context.select<UserNotifier, String>((u) => u.name); // several fields: records compare by valuefinal (count, total) = context.select<CartNotifier, (int, double)>(  (c) => (c.count, c.total),…
  9. 09

    Find the bug: after add(), the cart list never shows the new item, although the notifier does change.

    class CartNotifier extends ChangeNotifier {  final List<String> _items = [];  List<String> get items => _items;   void add(String item) {    _items.add(item);    notifyListeners();  }} // in CartList.buildWidget build(BuildContext context) {  final items = context.select<CartNotifier, List<String>>((c) => c.items);  return Column(children: [for (final name in items) Text(name)]);}

    add() mutates the same List in place, and select compares the list it returned last time with the one it returns now.

  10. 10

    What is ProxyProvider, and what goes wrong if ChangeNotifierProxyProvider.update returns a new notifier every time?

    ProxyProvider builds a value from other providers and calls update again whenever one of them changes.

    MultiProvider(  providers: [    ChangeNotifierProvider(create: (_) => AuthNotifier()),     // immutable service: a new instance per token is fine    ProxyProvider<AuthNotifier, ApiService>(…
  11. 11

    What causes "A ChangeNotifier was used after being disposed", and how do you prevent it?

    Something called notifyListeners() on a notifier whose dispose() already ran: almost always an async callback that finished after the screen was gone.

    class FeedNotifier extends ChangeNotifier {  FeedNotifier(this._repo) {    _sub = _repo.updates().listen((_) => notifyListeners());  }   final FeedRepository _repo;…
  12. 12

    This page crashes with "setState() or markNeedsBuild() called during build". Why, and where should load() be called instead?

    class FeedNotifier extends ChangeNotifier {  FeedNotifier(this._repo);  final FeedRepository _repo;  List<Post> posts = const [];  Future<void> load() async {    notifyListeners(); // e.g. show a spinner    posts = await _repo.fetch();    notifyListeners();  }}// FeedList watches FeedNotifierWidget build(BuildContext context) {  context.read<FeedNotifier>().load();  return const FeedList();}

    load() calls notifyListeners() synchronously, before its first await.

  13. 13

    A dialog opened with showDialog throws ProviderNotFoundException. Why, and how do you fix it?

    A dialog is a new route built by the Navigator's overlay, not by the widget that called showDialog, so its context does not descend from a provider you placed inside the page.

    // screen-scoped state: capture it, then re-expose inside the routeonPressed: () {  final cart = context.read<CartNotifier>(); // still inside the page  showDialog(    context: context,    builder: (_) => ChangeNotifierProvider<CartNotifier>.value(…
  14. 14

    Where do you show a snackbar or navigate when a notifier's state changes, given Provider has no BlocListener?

    Outside build: subscribe with addListener in initState and react to transitions, or model the event as data the widget consumes once.

    class _CheckoutPageState extends State<CheckoutPage> {  late final CheckoutNotifier _notifier;  late CheckoutStatus _last;  @override  void initState() {    super.initState();…
  15. 15

    Find the bug: this page throws every time the user leaves it.

    class _OrdersPageState extends State<OrdersPage> {  @override  void initState() {    super.initState();    context.read<OrdersNotifier>().addListener(_onChanged);  }  void _onChanged() {    if (!context.read<OrdersNotifier>().failed) return;    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Failed')));  }  @override  void dispose() {    context.read<OrdersNotifier>().removeListener(_onChanged);    super.dispose();  }}

    dispose() looks the notifier up through context, but by then the element is deactivated and may no longer search its ancestors.

  16. 16

    How do you test a screen that depends on Provider?

    At two levels: the notifier on its own as plain Dart with mocked dependencies, and the screen in a widget test with the notifier injected through ChangeNotifierProvider.value.

    class MockCartApi extends Mock implements CartApi {} void main() {  late MockCartApi api;  setUp(() => api = MockCartApi());…
  17. 17

    How does Riverpod differ from Provider, which Provider problems does it fix, and when would you still pick Provider?

    Riverpod is the redesign by the same author: providers are global typed objects, not looked up through the widget tree by type, so there is no ProviderNotFoundException and no BuildContext requirement.

  18. 18

    Walk me through what actually happens when a widget calls context.watch<T>().

    watch<T>() registers your element as a dependent of the provider's inherited element, which itself listens to the value.

  19. 19

    After one user signs out and another signs in, the app still shows the first user's data. How do you reset providers that live above MaterialApp?

    Destroy the subtree that owns them instead of resetting each notifier by hand: put user-scoped providers under a widget keyed by the user id, so a new id unmounts the old ones, disposes them and runs create again.

    class SignedInScope extends StatelessWidget {  const SignedInScope({super.key, required this.child});  final Widget child;   @override  Widget build(BuildContext context) {…
  20. 20

    A method mutates ten fields and calls notifyListeners() after each one. How many widget rebuilds does that cause, and what does it really cost?

    Usually one rebuild per dependent: a notification only marks things dirty, and a dirty element is rebuilt once in the next frame.

  21. 21

    A notifier above MaterialApp ticks once a second, several screens watch it, and the app gets janky the deeper users navigate. Why does stack depth matter?

    Pushing a route does not unmount the screens under it.