Migration to Flutter Guide
Discover our battle-tested 21-step framework for a smooth and successful migration to Flutter!
Home
Glossary

Provider package in Flutter

What is Provider package in Flutter?

The provider package is a pub.dev dependency for Flutter that wraps InheritedWidget and ChangeNotifier in a simpler API, giving apps context.watch, context.read, and MultiProvider for everyday state management.

It isn't part of the Flutter SDK, so it needs adding to pubspec.yaml before use. Note that path_provider is a different, unrelated package for finding filesystem directories – the name overlap with "provider" is a common source of confusion.

Key facts at a glance

  • The provider package must be added to pubspec.yaml; it isn't bundled with the Flutter SDK.
  • Providers that offer both create and value constructors use them for different lifecycle scenarios: create is for creating a new value that the provider can manage and dispose, while value is for exposing an existing value whose lifecycle is managed elsewhere.
  • context.watch<T>() subscribes the calling widget to changes and rebuilds it when the provided value changes; context.read<T>() accesses the value without subscribing to changes.
  • context.read<T>() should be called in callbacks such as onPressed when you need to access a provider without rebuilding the widget in response to its changes.
  • MultiProvider lets you declare multiple providers in a list instead of manually nesting them; internally, it builds the equivalent nested provider structure.

How do I use the provider package in Flutter?

Wrap the relevant part of the widget tree in a ChangeNotifierProvider, then read the model with context.watch or context.read.

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

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

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

class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => CartModel(),
      child: const CartView(),
    );
  }
}

class CartView extends StatelessWidget {
  const CartView({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const CartHeader(),
      ),
      body: Center(
        child: Consumer<CartModel>(
          builder: (context, cart, child) {
            return Text('${cart.count} items');
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => context.read<CartModel>().add(),
        child: const Icon(Icons.add),
      ),
    );
  }
}

class CartHeader extends StatelessWidget {
  const CartHeader({super.key});

  @override
  Widget build(BuildContext context) {
    final count = context.watch<CartModel>().count;

    return Text('Cart ($count)');
  }
}

CartView is below the provider but doesn't subscribe to its changes. The Scaffold itself therefore doesn't rebuild when CartModel changes.

context.watch is used in CartHeader. Only CartHeader subscribes to CartModel, so it rebuilds when count changes.

context.read is used by the FloatingActionButton. It accesses the model to call add() when tapped without subscribing the button to changes.

The BuildContext matters. The context used by CartView is below ChangeNotifierProvider, so it can access CartModel. A context belonging to a widget above the provider cannot access it.

How do I share one provider across multiple models?

Wrap the relevant part of the widget tree in a ChangeNotifierProvider, then read the model with context.watch or context.read.

MultiProvider(
  providers: [
    Provider<CartModel>(create: (_) => CartModel()),
    Provider<UserModel>(create: (_) => UserModel()),
  ],
  child: const MyApp(),
),

Each provider in the list is independent – widgets below can read CartModel and UserModel separately, in any order.

Best practices

  1. Use context.watch inside build, and context.read inside callbacks – calling context.read from build won't rebuild the widget when the value changes later.
  2. Let create own disposal – models constructed through a provider's create callback are disposed automatically; don't call dispose() on them yourself.
  3. Use Consumer or context.select to scope rebuilds to just the widget that needs the data, instead of rebuilding a large subtree.
  4. Reach for MultiProvider as soon as a screen needs more than one provider, rather than nesting them by hand.
  5. Make sure the BuildContext you use is below the provider – a context can only access providers that are ancestors of its widget. If you try to call context.watch or context.read from a widget above the Provider, the provider won't be found.
  6. Keep provider scope as narrow as practical – place the Provider around the part of the widget tree that actually needs the value, rather than providing it higher in the tree without a reason.
  7. Choose a provider based on how the value changes. Use Provider for values that don't need to notify dependents, ListenableProvider for general Listenable objects, ChangeNotifierProvider for ChangeNotifier objects, ValueListenableProvider for ValueListenable objects, StreamProvider for values emitted by a Stream, and FutureProvider for values produced by a Future.
  8. Use the .value constructor when exposing an existing instance. Use Provider.value when the value is created and managed outside the provider, rather than by its create callback.

Common mistakes

  • Calling context.read inside the build method. It reads the value once and never rebuilds when it changes – use context.watch for anything the UI needs to react to.
  • Reading a provider from a BuildContext that isn't below the widget that created it – for example, calling context.watch<CartModel>() in the same build method that creates the ChangeNotifierProvider<CartModel> above it. context.watch, context.read, and Provider.of all search upward from the given context, so if that context sits at or above the provider, the package throws a ProviderNotFoundException. Fix it by accessing the provider from a descendant context – for example, extract the code into a child widget, wrap it in a Builder, or use Consumer so that the context used to access the provider is below it in the widget tree.
  • Manually disposing a model created through a provider's create callback. The provider owns its lifecycle and disposes of it automatically when removed from the widget tree, so don't dispose of the model yourself.
  • Providing state at the app level when only one screen needs it. This keeps the provider and its value alive longer than necessary; scope providers to the part of the widget tree that actually uses them.

Learn more

Design system Flutter

Building a Design System in a Large Flutter App

Flutter is known for its seamless UI/UX features and robust design elements. It allowed us to deliver a unique customer experience in the “CA24 Mobile” banking app. This article shares some of the learnings and pain points behind implementing it in this large project.