Flutter Provider State Managemen

Provider is the most popular state management package for Flutter. It solves the prop-drilling problem by storing shared data outside the widget tree. Any widget in the app can read or update that data directly.

The Problem Provider Solves

Without Provider, shared data travels down through every widget level (prop drilling). With Provider, data lives in a central store. Any widget reads it directly.

  Without Provider (prop drilling):
  ─────────────────────────────────
  App (user)
   └── HomeScreen (user)
       └── Header (user)
           └── Avatar (user) ← actually needs it

  With Provider (direct access):
  ─────────────────────────────────
  Provider (user data)
      ↑ any widget reads directly
  App
   └── HomeScreen
       └── Header
           └── Avatar ← reads from Provider directly

Step 1 — Add Provider to the Project

  In pubspec.yaml:
  ─────────────────
  dependencies:
    flutter:
      sdk: flutter
    provider: ^6.1.0

  Run: flutter pub get

Step 2 — Create a ChangeNotifier Class

A ChangeNotifier is the data store. It holds state and notifies listeners when something changes.

import 'package:flutter/material.dart';

class CartProvider extends ChangeNotifier {
  List<String> _items = [];

  List<String> get items => _items;
  int get count => _items.length;

  void addItem(String name) {
    _items.add(name);
    notifyListeners();  // Tells all listeners to rebuild
  }

  void removeItem(String name) {
    _items.remove(name);
    notifyListeners();
  }

  void clearCart() {
    _items.clear();
    notifyListeners();
  }
}
  CartProvider:
  ┌────────────────────────────────┐
  │ _items: ['Shoes', 'T-Shirt']   │
  │                                │
  │ addItem() → notifyListeners()  │
  │ count: 2                       │
  └────────────────────────────────┘
         ↑                  ↓
    Any widget          Any widget
    can update          can read

Step 3 — Wrap the App with ChangeNotifierProvider

Place the Provider above any widget that needs the data — usually at the top of the widget tree.

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CartProvider(),
      child: MyApp(),
    ),
  );
}

Step 4 — Read Data with Consumer or Provider.of

Using Consumer — Recommended

Consumer rebuilds only the widget inside it when notifyListeners() fires. This is more efficient than rebuilding the whole screen.

Consumer<CartProvider>(
  builder: (context, cart, child) {
    return Text('Cart: ${cart.count} items');
  },
)

Using Provider.of — For Quick Reads

// Rebuilds widget when data changes
CartProvider cart = Provider.of<CartProvider>(context);

// Read-only (does not rebuild on change)
CartProvider cart = Provider.of<CartProvider>(context, listen: false);
cart.addItem('Shoes');  // Trigger update without rebuilding current widget

Full Example — Shopping Cart with Provider

// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => CartProvider(),
      child: MaterialApp(home: HomeScreen()),
    ),
  );
}

// HomeScreen with product list
class HomeScreen extends StatelessWidget {
  final List<String> products = ['Shoes', 'T-Shirt', 'Watch'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Products'),
        actions: [
          // Cart badge in AppBar
          Consumer<CartProvider>(
            builder: (context, cart, _) {
              return Stack(
                children: [
                  IconButton(icon: Icon(Icons.shopping_cart), onPressed: () {}),
                  if (cart.count > 0)
                    Positioned(
                      right: 6, top: 6,
                      child: CircleAvatar(
                        radius: 8, backgroundColor: Colors.red,
                        child: Text('${cart.count}', style: TextStyle(fontSize: 10, color: Colors.white)),
                      ),
                    ),
                ],
              );
            },
          ),
        ],
      ),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(products[index]),
            trailing: ElevatedButton(
              onPressed: () {
                // Add to cart without listening
                Provider.of<CartProvider>(context, listen: false)
                    .addItem(products[index]);
              },
              child: Text('Add'),
            ),
          );
        },
      ),
    );
  }
}

Multiple Providers

When your app needs several independent data stores, use MultiProvider.

void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CartProvider()),
        ChangeNotifierProvider(create: (_) => UserProvider()),
        ChangeNotifierProvider(create: (_) => ThemeProvider()),
      ],
      child: MyApp(),
    ),
  );
}

Provider vs setState

AspectsetStateProvider
ScopeOne widget onlyAny widget in the tree
Best forLocal UI changesShared app-wide data
BoilerplateMinimalSlightly more setup
Rebuild controlRebuilds whole widgetConsumer rebuilds only its subtree

When to Use Provider

  • Cart data that AppBar and product list both need to read
  • Logged-in user info needed across many screens
  • Theme settings (dark/light mode)
  • Any data that flows through more than 2 widget levels

Leave a Comment

Your email address will not be published. Required fields are marked *