Flutter Provider: A Practical Guide to State Management with a Real-World Example

Amanullah Bahram
5 min readOct 22, 2024

--

If you’re diving deep into Flutter development, you’ve probably come across different ways to manage state in your app. For small projects, `setState()` may work just fine, but as your app grows in complexity, you’ll need a more robust and scalable solution. That’s where Provider comes in.

In this article, we’ll explore how the Provider package can simplify state management in Flutter apps. Instead of a theoretical overview, we’ll walk through a real-world example — a simple product catalog with a cart feature, where users can add products and view the total price.

By the end, you’ll have a solid understanding of how to:
- Set up and configure Provider in a Flutter app.
- Use `ChangeNotifierProvider` to manage state across multiple widgets.
- Implement `Consumer` and `Provider.of` to access state efficiently.

What is Provider?

Provider is one of the most popular state management solutions in Flutter. It provides an easy-to-use interface for sharing and updating state across multiple widgets, without the overhead of constantly rebuilding the entire widget tree.

The beauty of Provider is its simplicity and performance optimization. It ensures that only the parts of your UI that rely on the state will rebuild when changes occur. No more unnecessary refreshes!

Getting Started

Let’s start by setting up our project.

First, create a new Flutter project and add the Provider dependency in your `pubspec.yaml`:


dependencies:
flutter:
sdk: flutter
provider: ^6.1.2 # Make sure to check for the latest version

Once you’ve added the dependency, run `flutter pub get` to install it.

— -

Building Our Real-World Example

We’ll build a product catalog where users can add products to a shopping cart. This simple app will showcase how Provider manages state and updates the UI in response to user actions.

1. Creating Models

First, let’s define the models for our products and cart items. These will be used to store product information and manage the shopping cart.

class Product {
final String id;
final String name;
final double price;
Product({required this.id, required this.name, required this.price});
}

class CartItem {
final Product product;
int quantity;
CartItem({required this.product, this.quantity = 1});
}

Here, the `Product` class holds the product details, while `CartItem` holds the product and its quantity in the cart.

— -

2. Setting Up Providers

Now we need to set up our providers, which will manage the state of products and the cart.

ProductProvider

The `ProductProvider` class holds a list of products and provides them to the rest of the app:

class ProductProvider with ChangeNotifier {
List<Product> _products = [
Product(id: 'p1', name: 'Laptop', price: 1500),
Product(id: 'p2', name: 'Smartphone', price: 800),
Product(id: 'p3', name: 'Headphones', price: 200),
];
List<Product> get products => _products;
}

CartProvider

The `CartProvider` handles the state of the shopping cart, allowing items to be added or removed, while keeping track of the total price and quantity:

class CartProvider with ChangeNotifier {
Map<String, CartItem> _items = {};
Map<String, CartItem> get items => _items;
void addToCart(Product product) {
if (_items.containsKey(product.id)) {
_items.update(
product.id,
(existingItem) => CartItem(
product: existingItem.product,
quantity: existingItem.quantity + 1,
),
);
} else {
_items.putIfAbsent(
product.id,
() => CartItem(product: product),
);
}
notifyListeners();
}

void removeFromCart(String productId) {
_items.remove(productId);
notifyListeners();
}

double get totalPrice {
return _items.values.fold(0.0, (sum, item) {
return sum + (item.product.price * item.quantity);
});
}
}

In this provider, `addToCart()` and `removeFromCart()` are used to manage cart items, and both notify listeners to update the UI when changes occur.

— -

3. Wiring Providers to the App

Now that we’ve set up our providers, let’s wire them up in the main app. This is where Provider shines by allowing easy access to shared state throughout the widget tree.

In the `main.dart` file, we’ll set up a `MultiProvider` to manage the `ProductProvider` and `CartProvider`:

void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => ProductProvider()),
ChangeNotifierProvider(create: (context) => CartProvider()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
'/': (context) => ProductScreen(),
'/cart': (context) => CartScreen(),
},
),
),
);
}

— -

4. Building the Product Screen

Now let’s create a screen that displays the list of products and allows users to add items to their cart.

class ProductScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var productProvider = Provider.of<ProductProvider>(context);
var products = productProvider.products;

return Scaffold(
appBar: AppBar(
title: Text('Products'),
actions: [
Consumer<CartProvider>(
builder: (context, cart, child) {
return Badge(
child: IconButton(
icon: Icon(Icons.shopping_cart),
onPressed: () {
Navigator.pushNamed(context, '/cart');
},
),
label: Text(cart.items.length.toString()),
);
},
)
],
),
body: ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
Product product = products[index];
return ProductTile(product: product);
},
),
);
}
}

class ProductTile extends StatelessWidget {
final Product product;

ProductTile({required this.product});

@override
Widget build(BuildContext context) {
var cartProvider = Provider.of<CartProvider>(context, listen: false);

return ListTile(
title: Text(product.name),
subtitle: Text('\$${product.price.toStringAsFixed(2)}'),
trailing: IconButton(
icon: Icon(Icons.add_shopping_cart),
onPressed: () {
cartProvider.addToCart(product);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${product.name} added to cart!')),
);
},
),
);
}
}

This screen shows the list of products and lets users add items to their cart with a single tap.

— -

5. Creating the Cart Screen

Finally, let’s build a screen that displays the contents of the shopping cart, including the total price:

class CartScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var cartProvider = Provider.of<CartProvider>(context);
var cartItems = cartProvider.items.values.toList();

return Scaffold(
appBar: AppBar(
title: Text('Your Cart'),
),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: cartItems.length,
itemBuilder: (context, index) {
CartItem cartItem = cartItems[index];
return CartItemTile(cartItem: cartItem);
},
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Total: \$${cartProvider.totalPrice.toStringAsFixed(2)}',
style: TextStyle(fontSize: 20),
),
),
],
),
);
}
}

class CartItemTile extends StatelessWidget {
final CartItem cartItem;

CartItemTile({required this.cartItem});

@override
Widget build(BuildContext context) {
var cartProvider = Provider.of<CartProvider>(context, listen: false);

return ListTile(
title: Text(cartItem.product.name),
subtitle: Text(
'${cartItem.quantity} x \$${cartItem.product.price.toStringAsFixed(2)}',
),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () {
cartProvider.removeFromCart(cartItem.product.id);
},
),
);
}
}

The `CartScreen` updates automatically when items are added or removed, thanks to Provider’s state management.

— -

Final Thoughts

Congratulations! You’ve now built a simple product catalog app that uses the Provider package for state management in Flutter. You’ve learned how to:
- Set up and configure Provider in a Flutter app.
- Manage state with `ChangeNotifierProvider`.
- Access and update state efficiently using `Consumer` and `Provider.of`.

For more advanced use cases, you can explore features like `StreamProvider` or `FutureProvider`. The flexibility and simplicity of Provider make it a great choice for managing state in Flutter, especially for larger apps.

If you enjoyed this tutorial and want to dive deeper, feel free to check out the Provider Package or explore the with the GitHub Source complete code for this example.

Happy coding! 🎉

--

--

Amanullah Bahram
Amanullah Bahram

Responses (1)