Getting Started with Provider for State Management

Getting Started with Provider for State Management

What You’ll Learn

Today, we’ll take the next step up from ValueNotifier and learn about Provider. It’s a powerful and popular package that helps you manage and share state across different parts of your app without passing data through countless widget constructors.

The Core Idea: Provide and Consume

Provider works on a simple principle:

  1. Provide a piece of state (your data model) at the top of your widget tree.
  2. Consume or access that state from any widget further down the tree.

First, add provider to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.0 # Check for the latest version

Example: A Shared Counter

Let’s create a counter model that can be shared between a display widget and a button, even if they are far apart in the widget tree.

1. The Data Model (ChangeNotifier) This class holds the state and notifies listeners when it changes.

import 'package:flutter/material.dart';

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

  void increment() {
    _count++;
    notifyListeners(); // This tells listening widgets to rebuild
  }
}

2. Provide the Model Wrap a parent widget (like MaterialApp or a Scaffold) with ChangeNotifierProvider.

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

void main() {
  runApp(
    // Provide the model to the entire app
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Provider Demo')),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CounterDisplay(), // This widget will show the count
              const SizedBox(height: 20),
              IncrementButton(), // This widget will increase the count
            ],
          ),
        ),
      ),
    );
  }
}

3. Consume the State Now, our widgets can access the CounterModel.

// Widget to display the count
class CounterDisplay extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // context.watch listens for changes and rebuilds the widget
    final counter = context.watch<CounterModel>();
    return Text(
      'Count: ${counter.count}',
      style: Theme.of(context).textTheme.headlineMedium,
    );
  }
}

// Widget with the increment button
class IncrementButton extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // context.read gets the model without listening for changes
    // Ideal for callbacks like onPressed
    final counter = context.read<CounterModel>();
    return ElevatedButton(
      onPressed: () => counter.increment(),
      child: const Text('Increment'),
    );
  }
}

Try It Yourself

  1. Add a decrement() method to the CounterModel.
  2. Create a DecrementButton widget that calls this new method.
  3. Add a “Reset” button that sets the counter back to zero.

Challenge: Create a new screen and navigate to it. Can you display the same counter value on the new screen? (Hint: You can! Because the ChangeNotifierProvider is above MaterialApp).

Tip of the Day

watch vs. read vs. select: