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
| Aspect | setState | Provider |
|---|---|---|
| Scope | One widget only | Any widget in the tree |
| Best for | Local UI changes | Shared app-wide data |
| Boilerplate | Minimal | Slightly more setup |
| Rebuild control | Rebuilds whole widget | Consumer 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
