flutter-provider-basico-changenotifier

State Management with Provider and ChangeNotifier

  • 3 min

Provider is a package for sharing state between widgets.

Provider works like a “radio antenna.”

  1. You have a Station (your class with the data).
  2. You have a Transmission Tower (which injects the signal into the App).
  3. You have Radios (the widgets that listen to the signal and react).

Let’s set up this system step by step.

Install Provider

Add the package to your pubspec.yaml:

flutter pub add provider
Copied!

Create a ChangeNotifier

First, we need a class to hold our data. It’s not a Widget; it’s a pure Dart class that extends ChangeNotifier.

This class has two functionalities:

  1. Hold private data.
  2. Call the notifyListeners() method.

This method is the “bell.” When you execute it, you shout to the whole App: “Hey, I changed! Whoever is listening to me, redraw yourself!”.

Create a file providers/counter_provider.dart:

import 'package:flutter/foundation.dart';

class CounterProvider extends ChangeNotifier {
  // 1. The State (Private so no one touches it directly)
  int _count = 0;

  // 2. Getter to read the state
  int get count => _count;

  // 3. Method to modify the state
  void increment() {
    _count++;
    
    // 🔔 THE KEY! Notify the listeners
    notifyListeners(); 
  }
}
Copied!

If you forget to call notifyListeners(), the _count variable will change, but the screen will not update. This is the most common mistake.

Inject the State with ChangeNotifierProvider

Now we have the class, but the App doesn’t know it exists. We have to “inject” it into the widget tree.

In this example, we want the counter to be available throughout the entire application, so we wrap MaterialApp with a ChangeNotifierProvider. In other cases, it’s better to place it lower down to limit its scope.

In your main.dart:

import 'package:provider/provider.dart';
import 'providers/counter_provider.dart';

void main() {
  runApp(
    // Inject the provider at the top of the tree
    ChangeNotifierProvider(
      create: (_) => CounterProvider(),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget { ... }
Copied!

Now, any widget inside MyApp has access to the CounterProvider.

Consume with context.watch and context.read

Now comes the interesting change. We no longer receive data through the constructor. We simply request it from the widget tree using the context.

We have two ways to request data, and it’s good to know the difference:

A. context.watch<T>() (The Observer)

Used inside the build method. It means: “Give me the data, and if it changes, redraw me (do an automatic setState)”.

B. context.read<T>() (The Executor)

Used inside events (onPressed, onTap). It means: “Give me access to the class to execute a function, but do not redraw me if something changes”.

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Drawing Screen..."); // For debugging

    // 1. LISTEN (Watch): If the counter changes, this build runs again
    final counterProvider = context.watch<CounterProvider>();

    return Scaffold(
      appBar: AppBar(title: Text("Provider Example")),
      body: Center(
        child: Text(
          'Value: ${counterProvider.count}', // Read the value
          style: TextStyle(fontSize: 40),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 2. EXECUTE (Read): We just want to call the function
          // Do not use .watch here, or you'll get an error.
          context.read<CounterProvider>().increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}
Copied!

Delimit Rebuilds with Consumer

Sometimes, using context.watch at the beginning of build rebuilds a larger widget than necessary.

If you want to redraw only a small part (for example, only the number Text and not the rest of the screen), you can use the Consumer widget.

// In this example, the Scaffold does NOT redraw, only the Text
Scaffold(
  body: Center(
    child: Consumer<CounterProvider>(
      builder: (context, provider, child) {
        return Text('Value: ${provider.count}');
      },
    ),
  ),
  // ...
)
Copied!

To start, context.watch is usually more straightforward. Use Consumer when you want to clearly delimit which piece depends on the provider or to reuse its child argument.