Labs ICT
Pro Login

Provider in Action

Building a real Provider example.

Building a Counter App with Provider

Let's put everything together by building a counter app using Provider. We'll create a model class that holds the count, wrap the app in a ChangeNotifierProvider, and consume the state in widgets. This is a complete, working example you can build on.

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

void main() => runApp(
  ChangeNotifierProvider(
    create: (context) => CounterModel(),
    child: MyApp(),
  ),
);

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Counter',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: HomeScreen(),
    );
  }
}
Try it Yourself ->

Create the Model Class

Your model extends ChangeNotifier and contains all the state and logic. It's a plain Dart class with no dependency on Flutter widgets. This separation makes your state easy to test and reuse across different parts of your app.

class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

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

  void decrement() {
    if (_count > 0) {
      _count--;
      notifyListeners();
    }
  }

  void reset() {
    _count = 0;
    notifyListeners();
  }
}
Try it Yourself ->

Consume State in Widgets

Use context.watch to get the current state and rebuild when it changes. Use context.read when you only need to call a method without listening for changes. This keeps your rebuilds efficient — only the widgets that actually use the state get updated.

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Provider Counter')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // watch rebuilds this widget when count changes
            Consumer<CounterModel>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.count}',
                  style: TextStyle(fontSize: 64, fontWeight: FontWeight.bold),
                );
              },
            ),
            SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                // read doesn't listen — just calls the method
                ElevatedButton(
                  onPressed: () => context.read<CounterModel>().decrement(),
                  child: Icon(Icons.remove),
                ),
                SizedBox(width: 16),
                ElevatedButton(
                  onPressed: () => context.read<CounterModel>().increment(),
                  child: Icon(Icons.add),
                ),
                SizedBox(width: 16),
                OutlinedButton(
                  onPressed: () => context.read<CounterModel>().reset(),
                  child: Text('Reset'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
Try it Yourself ->

Building a Shopping Cart Example

Here's a more practical example — a simple shopping cart. The model holds a list of items, and widgets across the app can add items, remove items, and display the total. This is exactly the kind of scenario where Provider shines over passing state through constructors.

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

class CartModel extends ChangeNotifier {
  final List<CartItem> _items = [];

  List<CartItem> get items => List.unmodifiable(_items);
  int get itemCount => _items.length;
  double get total => _items.fold(0, (sum, item) => sum + item.price);

  void addItem(String name, double price) {
    _items.add(CartItem(name: name, price: price));
    notifyListeners();
  }

  void removeItem(int index) {
    _items.removeAt(index);
    notifyListeners();
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}

// Wrap your app:
ChangeNotifierProvider(
  create: (context) => CartModel(),
  child: MyApp(),
)

// In any widget, add items:
context.read<CartModel>().add('Flutter Book', 29.99);

// Display items:
Consumer<CartModel>(
  builder: (context, cart, child) {
    return Column(
      children: [
        for (var item in cart.items)
          ListTile(
            title: Text(item.name),
            trailing: Text('\$${item.price}'),
          ),
        Divider(),
        Text('Total: \$${cart.total}'),
      ],
    );
  },
)
Try it Yourself ->