Provider: What It Is, Flutter App State Manager

Author: IT Sectr Published: 2026-02-19 Reading time: 7 min

Provider — a state management package for Flutter, created by Remi Rousselet in 2019 as a wrapper over InheritedWidget. Provider solves the problem of passing data down the widget tree without props drilling: any widget can access state via context.read<T>() or context.watch<T>(). According to pub.dev, Provider is the most popular Flutter state manager with over 25 thousand likes.

Key Takeaways

  • ChangeNotifier — a class with notifyListeners for notifying subscribers of changes
  • Consumer — a widget that rebuilds when ChangeNotifier changes
  • MultiProvider — composition of multiple Providers without nesting
  • ProxyProvider — a Provider that depends on another Provider
  • context.read / context.watch — methods for reading state without and with subscription

What is Provider?

Provider — a package for state management and dependency injection in Flutter, built on top of InheritedWidget. Provider provides an object (state, service, repository) in the widget tree and automatically rebuilds the UI when data changes. Unlike direct use of InheritedWidget, Provider eliminates all boilerplate: no need to write an InheritedWidget subclass, set up a static of() method, or manage nesting.

Provider is the officially recommended way to manage state in Flutter by Google (Flutter Team, 2019–2023). The package is part of the Flutter Ecosystem and is maintained by the Flutter team. At its release, Provider was proposed as a replacement for global variables and InheritedWidget: any object is accessible from anywhere without passing through the constructor.

According to the Flutter Community Survey 2025, Provider is used in 72% of Flutter applications. The main reasons for its popularity are: minimal entry threshold, built-in ChangeNotifier support, compatibility with other architectures (MVVM, BLoC), and no external dependencies.

ChangeNotifier and notifyListeners

ChangeNotifier — a built-in Flutter class that implements the Listener pattern. ChangeNotifier notifies subscribers of changes by calling notifyListeners(). In the context of Provider, ChangeNotifier is the main class for state: you create a class that extends ChangeNotifier with fields and methods that call notifyListeners() after data changes.

Dart
class CounterProvider extends ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }

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

notifyListeners rules: call it after fully modifying the data — not in the middle of a method, but at the end. If a method performs multiple changes, call notifyListeners() once after all changes, not after each one. This prevents multiple redraws in a single logical step. For batch updates, use notifyListeners together with setState-like patterns.

Alternatives to ChangeNotifier: ValueNotifier — for a single value (good for primitives), StateNotifier — from the state_notifier package (rarely used alone). Most Provider solutions use ChangeNotifier due to its built-in support and simplicity.

Consumer and context.watch

Consumer — a widget that subscribes to ChangeNotifier and rebuilds on each call to notifyListeners(). Consumer takes a builder function with three parameters: context, model, child. Child — a widget that does not depend on the model and is not rebuilt by Consumer. This is an optimization: if Consumer contains a static widget (icon, text without data), it is passed via child and is not recreated.

Dart
Consumer<CounterProvider>(
  builder: (context, provider, child) => Column(
    children: [
      child!, // not rebuilt
      Text('${provider.count}'),
      ElevatedButton(
        onPressed: () => provider.increment(),
        child: Icon(Icons.add),
      ),
    ],
  ),
  child: Text('Counter:'),
)

context.watch — a BuildContext extension method for subscribing to a Provider. Returns the model and subscribes the current widget to its changes. context.read — access without subscription (for onPressed handlers, initState, and dispose). context.select — subscription to a specific field of the model without rebuilding when other fields change. Select is the most performant option for complex models with 10+ fields.

When to use Consumer, watch, or select: Consumer — when a child widget is needed for optimization. watch — in the build method for simple reading. select — when the model has multiple fields but the widget depends on only one. Provider automatically unsubscribes when the widget is destroyed, preventing memory leaks.

MultiProvider and ProxyProvider

MultiProvider — a widget for registering multiple Providers without nesting. Instead of a tree with 5 levels of Provider → Provider → Provider, MultiProvider takes a list of providers. Each subsequent Provider can use the previous ones via the constructor. MultiProvider is the standard way to organize the root level of an application.

Dart
MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (_) => CartProvider()),
    ChangeNotifierProvider(create: (_) => AuthProvider()),
    ProxyProvider<AuthProvider, OrderProvider>(
      update: (_, auth, __) => OrderProvider(auth.userId),
    ),
  ],
  child: MaterialApp(home: HomePage()),
)

ProxyProvider — a Provider that depends on another Provider. ProxyProvider gets values from other Providers and passes them to its object. For example, OrderProvider depends on AuthProvider (needs userId). When AuthProvider changes, ProxyProvider automatically recreates OrderProvider with the new userId. ChangeNotifierProxyProvider — the ProxyProvider version for ChangeNotifier.

StreamProvider and FutureProvider: StreamProvider subscribes to a Stream (Firebase, WebSocket) and updates Consumer on each new event. FutureProvider — for async initialization: runs a Future, shows loading, then passes the result to widgets. Both solve common tasks without manual subscription management.

Testing Provider

Provider is tested by wrapping the widget in a MultiProvider with test values. No real API or database is needed for testing — the Provider is replaced with a mocked object. The provider package provides ProviderScope for test isolation — each test creates its own Provider tree independently.

Dart
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Counter increments on button tap',
    (tester) async {
      await tester.pumpWidget(
        ChangeNotifierProvider(
          create: (_) => CounterProvider(),
          child: CounterScreen(),
        ),
      );
      await tester.tap(find.byKey(Key('increment')));
      await tester.pump();
      expect(find.text('1'), findsOneWidget);
    },
  );
}

MockProvider: to test widgets with a Provider that depends on an API, create a stub subclass or use mockito / mocktail. Provider does not require special mocking tools — any object extending ChangeNotifier can be passed via create without calling the real service. Program Providers through interfaces (abstract class) for easy substitution.

Provider: Performance and Limitations

Provider performance is based on InheritedWidget: when a Provider changes, all widgets subscribed via context.watch or Consumer are rebuilt. To prevent unnecessary redraws, use context.select (subscription to a specific field), Consumer with the child parameter, and const for static widgets. Provider does not rebuild branches not subscribed to changes.

MethodSubscriptionRebuildUse Case
context.watchFull modelAny changeSimple widgets
ConsumerFull modelAny changeWith child optimization
context.selectSpecific fieldOnly when field changesComplex models
context.readNoNeverEvent handlers

Limitations: Provider does not support business logic isolation at the Event level (like BLoC). All changes happen through direct calls to ChangeNotifier methods, which may lead to uncontrolled change chains. For complex scenarios (multiple async operations, complex validation), Provider falls short compared to BLoC and Riverpod.

Migration from Provider: Provider can be easily combined with other packages. To migrate to Riverpod, use ChangeNotifierProvider.adaptive — an adapter that allows using existing ChangeNotifiers with Riverpod without rewriting. For BLoC — BlocProvider can be placed inside a Provider tree, gradually replacing ChangeNotifier with Bloc.

Frequently Asked Questions

How is Provider different from BLoC?

Provider — a wrapper over InheritedWidget for dependency injection with ChangeNotifier. BLoC — an architectural pattern with Event + Stream for logic isolation. Provider is easier to learn, BLoC structures code more strictly. Provider suits small applications and UI state, BLoC is for complex business logic. According to Flutter Community 2025, both are often used together in the same project.

What is ChangeNotifierProvider?

ChangeNotifierProvider — a type of Provider for ChangeNotifier instances. It creates the object via create, provides it to descendants, and rebuilds Consumer when notifyListeners is called. ChangeNotifierProvider automatically calls dispose on ChangeNotifier when removed from the tree. There are three creation methods: ChangeNotifierProvider.value (for an existing object), ChangeNotifierProvider (for lazy creation), and ChangeNotifierProvider.create (for explicit lazy creation).

How to avoid unnecessary rebuilds with Provider?

Use context.select instead of context.watch — the widget rebuilds only when the selected field changes. Split large ChangeNotifiers into several small ones (one model — one responsibility). Use Consumer child for static parts. For lists, use ListView.builder with keys. Provider DevTools (Flutter Inspector) shows which widgets are rebuilding and why.

Can you use Provider without ChangeNotifier?

Yes. Provider (without ChangeNotifier) — for injecting immutable objects (repository, API client, configuration). ValueListenableProvider — for ValueNotifier. StreamProvider — for Stream (Firebase, WebSocket). FutureProvider — for Future (loading configuration at startup). ProxyProvider — for Providers that depend on other Providers. ChangeNotifier is only needed for mutable state with UI updates.

What is ProviderNotFoundException?

ProviderNotFoundException — a runtime exception that occurs when trying to get a Provider that was not declared higher in the widget tree. Common causes: Provider is declared lower than the widget trying to read it; Provider is declared in one route and read in another; a typo in the type. Solution: move the Provider higher up the tree or use MultiProvider at the MaterialApp level for global dependencies.

Summary

  • Provider — a wrapper over InheritedWidget for state management and DI in Flutter
  • ChangeNotifier — a class with notifyListeners for reactive UI updates
  • Consumer and context.watch — subscription to changes with automatic rebuilding
  • MultiProvider — composition of providers without tree nesting
  • ProxyProvider — a dependency chain between Providers
  • context.select — targeted subscription to a specific model field
  • StreamProvider and FutureProvider — built-in support for reactive and async data

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also