iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Provider

Provider is Flutter’s mainstream state-management library: ChangeNotifierProvider exposes a model, Consumer/Selector subscribes to it, dependency injection happens via Provider.of or context.watch/read. It’s the gentle on-ramp before Riverpod / Bloc / Redux.

ChangeNotifier, Consumer, Selector, MultiProvider

EXAMPLE
// 1) Install
// pubspec.yaml
// dependencies:
//   provider: ^6.1.2

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

// 2) A simple ChangeNotifier model
class CounterModel extends ChangeNotifier {
    int _count = 0;
    int get count => _count;

    void increment() {
        _count++;
        notifyListeners();           // tells watchers to rebuild
    }

    void reset() {
        _count = 0;
        notifyListeners();
    }
}

// 3) Provide + Consume
void main() {
    runApp(
        ChangeNotifierProvider(
            create: (_) => CounterModel(),
            child: const MyApp(),
        ),
    );
}

class MyApp extends StatelessWidget {
    const MyApp({super.key});
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            home: Scaffold(
                appBar: AppBar(title: const Text('Counter')),
                body: const CounterDisplay(),
                floatingActionButton: FloatingActionButton(
                    onPressed: () => context.read<CounterModel>().increment(),
                    child: const Icon(Icons.add),
                ),
            ),
        );
    }
}

class CounterDisplay extends StatelessWidget {
    const CounterDisplay({super.key});
    @override
    Widget build(BuildContext context) {
        final count = context.watch<CounterModel>().count;
        return Center(child: Text('Count: $count', style: const TextStyle(fontSize: 36)));
    }
}

// 4) context.watch / read / select
// context.watch<T>()      — subscribes; rebuilds when notify fires
// context.read<T>()       — no subscription; for callbacks / one-shot reads
// context.select<T, R>()  — subscribes, but rebuilds only when the selector value changes

class NameView extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        final name = context.select<User, String>((u) => u.name);
        return Text(name);
    }
}

// 5) MultiProvider — many models
MultiProvider(
    providers: [
        ChangeNotifierProvider(create: (_) => AuthModel()),
        ChangeNotifierProvider(create: (_) => CartModel()),
        Provider<ApiClient>(create: (_) => ApiClient()),
        ProxyProvider<AuthModel, ApiClient>(
            update: (_, auth, prev) => ApiClient(token: auth.token),
        ),
    ],
    child: const App(),
);

// 6) Consumer widget — alternative when you want explicit scope
Consumer<CounterModel>(
    builder: (context, model, child) {
        return Text('Count: ${model.count}');
    },
)

// 7) Consumer with a non-rebuilding child
Consumer<CounterModel>(
    builder: (context, model, child) {
        return Column(
            children: [
                Text('Count: ${model.count}'),
                child!,                                    // doesn't rebuild
            ],
        );
    },
    child: const ExpensiveStaticWidget(),
)

// 8) Future + Stream providers
FutureProvider<User>(
    create: (_) => api.fetchUser(),
    initialData: User.empty(),
    child: const UserView(),
);

StreamProvider<int>(
    create: (_) => Stream.periodic(const Duration(seconds: 1), (i) => i),
    initialData: 0,
    child: const TickerView(),
);

// 9) Scoped providers
// Place a Provider deep in the tree → only descendants see it.
// Multiple instances of the same provider type exist in different subtrees.

class NotesScreen extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return ChangeNotifierProvider(
            create: (_) => NotesModel(),
            child: const NotesPage(),
        );
    }
}

// 10) Provider DISPOSE — automatic
// ChangeNotifierProvider calls notifier.dispose() when it leaves the tree.

// 11) Testing
import 'package:flutter_test/flutter_test.dart';

testWidgets('counter increments', (tester) async {
    final model = CounterModel();
    await tester.pumpWidget(
        ChangeNotifierProvider.value(
            value: model,
            child: const MaterialApp(home: CounterDisplay()),
        ),
    );
    expect(find.text('Count: 0'), findsOneWidget);
    model.increment();
    await tester.pump();
    expect(find.text('Count: 1'), findsOneWidget);
});

// 12) ChangeNotifier vs ValueNotifier vs custom
// • ValueNotifier<T> — single value; lightweight
// • ChangeNotifier — many fields; getters; coarse-grained
// • Custom — extend ChangeNotifier; implement equality if using Selector

// 13) Avoid common pitfalls
// • Calling notifyListeners from build → infinite loop
// • Reading provider in initState — use addPostFrameCallback or context.read
// • Using watch in callbacks (onPressed) — overrebuilds; use read
// • Forgetting select when only one field matters → unnecessary rebuilds
// • Creating model in build() instead of provider.create → model recreated every frame

// 14) Provider vs Riverpod
// Provider (the package above) is the original. Riverpod is the same author's modern successor:
//   • Compile-time safety (typed providers, no inheritedWidget lookups failing at runtime)
//   • Easier testing + overrides
//   • Works outside the widget tree
// For NEW projects, prefer Riverpod. For existing Provider codebases, both are fine.

// 15) Common bugs
// • 'ProviderNotFoundException' — calling Provider.of above its declaration; check tree
// • Model rebuilds parent → use Selector to scope rebuilds
// • Dispose called too early (Hot reload) → ChangeNotifier may emit during HMR; harmless
// • Multiple providers of the same type at different levels — closest one wins; surprising
// • Mutating state without notifyListeners → UI doesn't update
// • Forgot to wrap with ChangeNotifierProvider.value when re-using existing model in tests

Why it matters

Provider gives you DI + observable models with very little ceremony: ChangeNotifierProvider at the top, context.watch/select for subscriptions, context.read in callbacks, MultiProvider for multiple models. Reach for Selector to scope rebuilds, and consider Riverpod for new apps — same mental model, with compile-time safety and easier testing.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
ChangeNotifierProvider(
    create: (_) => CounterModel(),
    child: const MyApp(),
);
// inside a widget
final model = context.watch<CounterModel>();
Try it Yourself »

Discussion

Loading…