flutter-provider-consumo-optimizado

Consuming Provider with watch, read, select and Consumer

  • 4 min

The state consumption is the way a widget reads shared data.

In the previous article, we saw how to connect a screen to a Provider using context.watch. It worked, but it had a dangerous “side effect”.

If you put a context.watch at the beginning of the build method, any call to notifyListeners will cause that widget to re-execute build.

If your screen is simple, it’s fine. But if you have a list of 100 items, animations, or heavy images, rebuilding everything just because a simple counter changed is a waste of battery and CPU.

Let’s learn how to rebuild the UI selectively.

Listening with context.watch<T>()

This is the most straightforward way. It listens to the entire provider.

  • Behavior: If the provider calls notifyListeners(), the widget where this line is located gets rebuilt.
  • Use case: Ideal when the widget is small and everything in its content depends on that data.
@override
Widget build(BuildContext context) {
  // ⚠️ WARNING: If UserProvider changes, THIS ENTIRE build executes again
  final user = context.watch<UserProvider>();

  return Scaffold(
    body: Center(child: Text(user.name)),
  );
}
Copied!

Accessing without listening with context.read<T>()

This is the opposite of watch. It retrieves the provider but closes its ears.

  • Behavior: It accesses methods and properties, but never triggers a rebuild, even if the data changes.
  • Use case: Mandatory inside events like onPressed or onTap.
ElevatedButton(
  onPressed: () {
    // ✅ CORRECT: We use read to execute actions
    context.read<UserProvider>().changeName("Luis");
    
    // ❌ ERROR: Never use watch here, it will throw a runtime error
    // context.watch<UserProvider>()... 
  },
  child: Text("Change"),
)
Copied!

Selecting a value with context.select<T, R>()

Now we fine-tune things a bit more.

Imagine your UserProvider has name (which rarely changes) and age (which changes a lot). If you use watch, your widget will repaint when age changes, even though you are only displaying the name.

select allows you to listen to only one specific property.

@override
Widget build(BuildContext context) {
  // 🪄 MAGIC: Only rebuilds if 'name' changes.
  // If 'age' changes, this widget ignores the change.
  final nombre = context.select<UserProvider, String>((provider) => provider.name);

  return Text(nombre);
}
Copied!

This is the most powerful tool for optimizing complex screens without splitting everything into small widgets.

Delimiting with Consumer<T>

The Consumer widget is an alternative to context.watch that allows us to visually isolate which part of the tree gets rebuilt.

It’s useful when you have a parent widget that is very expensive to build (with animations or complex gradients) and you only want to change a small piece of text inside it.

The child parameter

Notice that Consumer has a child parameter in its constructor, which is then passed to the builder. This confuses a lot of people, but it’s great.

That child is the static part that we do NOT want to rebuild.

Scaffold(
  body: Center(
    child: Consumer<UserProvider>(
      // 1. Define what DOES NOT change (The static Child)
      child: Image.asset('assets/heavy_logo.png'), 
      
      // 2. The Builder receives that already built 'child'
      builder: (context, provider, child) {
        print("Rebuilding only the text...");
        return Column(
          children: [
            child!, // The builder does not rebuild this instance again
            Text("Hello ${provider.name}"), // This does change
          ],
        );
      },
    ),
  ),
)
Copied!

In this example, the builder receives the same instance of the image and does not rebuild it when the name changes. This does not mean it never participates in layout or painting, as those phases depend on the rest of the tree.

Comparison

MethodSyntaxRebuilds?When to use it?
Watchcontext.watch<T>()✅ YES (Entire widget)When you need to listen to the entire provider.
Readcontext.read<T>()❌ NOInside onPressed or onTap functions.
Selectcontext.select<T, R>(fn)✅ YES (Selective)When you only care about one field (provider.age).
ConsumerConsumer<T>(...)✅ YES (Isolated)When you want to protect the parent from rebuilds or use the child argument to optimize.

Which option to choose

context.watch and context.select keep the code flat. Consumer is useful when you want to visually limit the dependency or take advantage of its child argument. Choose the option that makes it most obvious which data each widget is listening to.