State — Provider
Provider package, ChangeNotifier, Consumer, context.watch/read/select
- 01
Explain Provider: ChangeNotifier, Consumer, context.watch vs context.read vs context.select.
MediumProvider wraps
InheritedWidgetwith 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); … - 02
Explain ProxyProvider and how to combine providers.
MediumProxyProvider 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 { … - 03
Why pick Provider over Riverpod or vice versa?
EasyProvider and Riverpod are made by the same author.
// ── Provider — runtime safety hole ── // Setup MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => AuthNotifier()), ], … - 04
What is MultiProvider and what are the lifecycle controls (lazy / dispose)?
MediumMultiProvider flattens nested Provider widgets into one declaration.
// ── MultiProvider — clean structure ── void main() { runApp( MultiProvider( providers: [ // Singletons available everywhere — eager so init errors surface immediately … - 05
How do StreamProvider and FutureProvider work, and when should you use them?
MediumThe 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>( … - 06
How do you test a screen that depends on Provider?
MediumA 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); … - 07
What is ListenableProvider and when do you reach for it instead of ChangeNotifierProvider?
MediumMost 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); … - 08
How do you avoid unnecessary rebuilds with context.select and Selector?
MediumWatching 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; … - 09
What problems with Provider does Riverpod aim to solve?
HardProvider 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
Walk me through what actually happens when a widget calls context.watch<T>().
HardProvider is a thin layer over InheritedWidget, so
watchis 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
A dialog opened with showDialog throws ProviderNotFoundException. Why, and how do you fix it?
MediumA 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
What causes the "A ChangeNotifier was used after being disposed" error, and how do you prevent it?
EasySomething 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
You expose a mutable Settings object with a plain Provider<Settings> and change a field on it, but nothing rebuilds — why?
EasyProvider<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
Items added on the products screen are missing on the cart screen, and both screens wrap themselves in ChangeNotifierProvider — what went wrong?
EasyEach
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
A screen crashes with "setState() or markNeedsBuild() called during build" and its build() calls context.read<FeedNotifier>().load() — what is happening?
Mediumload()callsnotifyListeners()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
In a ChangeNotifierProxyProvider, what breaks if update returns a brand-new notifier every time its dependency changes?
MediumYou 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
Where do you put a snackbar or a navigation that must happen when a notifier's state changes, given Provider has no BlocListener?
MediumOutside
build— subscribe to the notifier ininitStatewithaddListener, or model the event as data the widget consumes, becausebuildmust 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
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?
MediumDestroy 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
A method mutates ten fields and calls notifyListeners() after each one — how many widget rebuilds does that cause, and what does it actually cost?
HardOne 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
A notifier above MaterialApp ticks once a second and the app grows janky the deeper users navigate — why does the stack depth matter?
HardPushing 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 …