State — Provider
Irbisa · cheatsheetSeptember 13, 2026

State — Provider

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

Middle Developer20 itemscompressed for a skim
  1. 01

    Explain Provider: ChangeNotifier, Consumer, context.watch vs context.read vs context.select.

    Medium

    Provider wraps InheritedWidget with a simpler API for DI and state management.

    // 1. ChangeNotifier model
    class CartNotifier extends ChangeNotifier {
      final List<Product> _items = [];
      List<Product> get items => List.unmodifiable(_items);
      int get count => _items.length;
      double get total => _items.fold(0, (s, p) => s + p.price);
    …
  2. 02

    Explain ProxyProvider and how to combine providers.

    Medium

    ProxyProvider creates a provider that depends on other providers.

    // Setup: ApiService depends on AuthToken
    class AuthToken extends ChangeNotifier {
      String? _token;
      String? get token => _token;
    
      Future<void> login(String email, String pass) async {
    …
  3. 03

    Why pick Provider over Riverpod or vice versa?

    Easy

    Provider and Riverpod are made by the same author.

    // ── Provider — runtime safety hole ──
    // Setup
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => AuthNotifier()),
      ],
    …
  4. 04

    What is MultiProvider and what are the lifecycle controls (lazy / dispose)?

    Medium

    MultiProvider flattens nested Provider widgets into one declaration.

    // ── MultiProvider — clean structure ──
    void main() {
      runApp(
        MultiProvider(
          providers: [
            // Singletons available everywhere — eager so init errors surface immediately
    …
  5. 05

    How do StreamProvider and FutureProvider work, and when should you use them?

    Medium

    The provider package ships wrappers for Streams and Futures, so you don't have to hand-roll a ChangeNotifier around them.

    import 'package:provider/provider.dart';
    
    // ── FutureProvider — config loaded once ──
    MultiProvider(
      providers: [
        FutureProvider<RemoteConfig>(
    …
  6. 06

    How do you test a screen that depends on Provider?

    Medium

    A Provider-backed screen is tested at two levels: the notifier on its own, and the widget with a fake notifier injected above it.

    // ── 1) Notifier under test ──
    class CartNotifier extends ChangeNotifier {
      CartNotifier(this._api);
      final CartApi _api;
      List<Item> _items = [];
      List<Item> get items => List.unmodifiable(_items);
    …
  7. 07

    What is ListenableProvider and when do you reach for it instead of ChangeNotifierProvider?

    Medium

    Most code uses ChangeNotifierProvider because ChangeNotifier is the most common form of Listenable.

    import 'package:flutter/foundation.dart';
    import 'package:provider/provider.dart';
    
    // ── ListenableProvider with a ValueNotifier ──
    final _theme = ValueNotifier<ThemeMode>(ThemeMode.system);
    …
  8. 08

    How do you avoid unnecessary rebuilds with context.select and Selector?

    Medium

    Watching a notifier rebuilds the consumer EVERY time the notifier calls notifyListeners() — regardless of which field changed.

    // ── Coarse state — User has many fields ──
    class UserNotifier extends ChangeNotifier {
      String _name = 'Ada';
      int _age = 30;
      String _email = 'a@x.com';
      String get name => _name;
    …
  9. 09

    What problems with Provider does Riverpod aim to solve?

    Hard

    Provider is a thin layer over InheritedWidget.

    // ── 1) Compile-time safety ──
    
    // Provider — runtime crash if not wired up
    final n = Provider.of<AuthNotifier>(context); // ❌ ProviderNotFoundException
    
    // Riverpod — type-checked, value-based reference
    …
  10. 10

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

    Hard

    Provider is a thin layer over InheritedWidget, so watch is nothing more than an inherited-widget dependency plus a listener on the value.

    // Roughly what context.watch<T>() expands to inside the provider package
    T watch<T>(BuildContext context) {
      final element = context
          .dependOnInheritedWidgetOfExactType<_InheritedProviderScope<T?>>();
      return element!.value as T;      // dependency registered -> rebuild on notify
    }
    …
  11. 11

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

    Medium

    A dialog is built by the Navigator's overlay, not by the widget that called showDialog, so its context sits outside your page's provider subtree.

    // ❌ Provider lives inside the page, dialog is built by the Navigator above it
    class CartPage extends StatelessWidget {
      const CartPage({super.key});
      @override
      Widget build(BuildContext context) {
        return ChangeNotifierProvider(
    …
  12. 12

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

    Easy

    Something called notifyListeners() on a notifier whose dispose() has already run, 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;
    …
  13. 13

    You expose a mutable Settings object with a plain Provider<Settings> and change a field on it, but nothing rebuilds — why?

    Easy

    Provider<T> is a value holder with no subscription: it rebuilds dependents when the exposed instance changes, and mutating a field inside the object it already holds is not an event it can see.

    // ❌ Plain Provider + a mutable object: nothing observes the mutation
    class Settings {
      bool darkMode = false;
    }
    
    Provider<Settings>(
    …
  14. 14

    Items added on the products screen are missing on the cart screen, and both screens wrap themselves in ChangeNotifierProvider — what went wrong?

    Easy

    Each ChangeNotifierProvider(create: ...) builds its own object, so the two screens are talking to two different carts.

    // ❌ Two owners of the same type — two carts
    class ProductsPage extends StatelessWidget {
      const ProductsPage({super.key});
      @override
      Widget build(BuildContext context) => ChangeNotifierProvider(
            create: (_) => CartNotifier(),     // instance A
    …
  15. 15

    A screen crashes with "setState() or markNeedsBuild() called during build" and its build() calls context.read<FeedNotifier>().load() — what is happening?

    Medium

    load() calls notifyListeners() synchronously, and notifying while the framework is in the build phase tries to dirty elements that may already have been built in this pass — exactly what that assertion forbids.

    class FeedNotifier extends ChangeNotifier {
      FeedNotifier(this._repo);
      final FeedRepository _repo;
      bool loading = false;
      List<Post> posts = const [];
    …
  16. 16

    In a ChangeNotifierProxyProvider, what breaks if update returns a brand-new notifier every time its dependency changes?

    Medium

    You leak the old notifier and throw away its state: the provider disposes only the value it is holding when it leaves the tree, so every instance you replaced is never disposed at all.

    class FeedNotifier extends ChangeNotifier {
      FeedNotifier(this._repo);
      final FeedRepository _repo;
      String? _token;
      Timer? _poll;
      List<Post> posts = const [];
    …
  17. 17

    Where do you put a snackbar or a navigation that must happen when a notifier's state changes, given Provider has no BlocListener?

    Medium

    Outside build — subscribe to the notifier in initState with addListener, or model the event as data the widget consumes, because build must be a pure function of state and runs for reasons that have nothing to do with your event.

    class _CheckoutPageState extends State<CheckoutPage> {
      late final CheckoutNotifier _notifier = context.read<CheckoutNotifier>();
      late CheckoutStatus _last;
    
      @override
      void initState() {
    …
  18. 18

    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?

    Medium

    Destroy the subtree that owns them instead of resetting each notifier by hand: root providers are created once and live as long as the app, and the structural fix is the one nobody can forget to update.

    // Root: session-independent only — these outlive every sign-in
    runApp(MultiProvider(
      providers: [
        Provider<Dio>(create: (_) => createDio()),
        ChangeNotifierProvider(create: (_) => AuthNotifier()),
      ],
    …
  19. 19

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

    Hard

    One rebuild per frame no matter how many notifications you send — but ten synchronous passes over every listener, and that is where the bill is.

    class ProfileNotifier extends ChangeNotifier {
      String name = '';
      String email = '';
      int age = 0;
    
      // ❌ Three synchronous dispatches; the UI still rebuilds exactly once
    …
  20. 20

    A notifier above MaterialApp ticks once a second and the app grows janky the deeper users navigate — why does the stack depth matter?

    Hard

    Pushing a route does not remove the screens under it: with maintainState: true, the default, they stay mounted and stay subscribed, so every tick rebuilds every screen in the stack rather than just the visible one.

    // A notifier above MaterialApp: every mounted route can depend on it
    class ClockNotifier extends ChangeNotifier {
      ClockNotifier() {
        _timer = Timer.periodic(const Duration(seconds: 1), (_) {
          now = DateTime.now();
          notifyListeners();          // marks EVERY dependent in the stack dirty
    …