Understand how listen: false works when used with Provider<SomeType>.of(context, listen: false)

Viewed 18026

I'm working through understanding how the Provider package works with Flutter but confused on how listen:false works.

I wrote some basic code using the usual Counter example from a new Flutter project. I created three types of stateless widgets each using a Provider:

  1. Provider.of(context)
  2. Consumer
  3. Provider.of(context, listen: false)

The third example was to show how access the provider object and call methods on it without it rebuilding.

When I run the application all of the widget counts are changing - and I only expect it to change in the first two.

This is a simple example - what am I doing wrong?

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

void main() => runApp(MyApp());

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(builder: (_) => Counter()),
      ],
      child: MaterialApp(
        title: 'Provider Demo',
        theme: ThemeData(
          primarySwatch: Colors.amber,
        ),
        home: MyHomePage(title: 'Provider Demo Home Page'),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  Widget build(BuildContext context) {
    Counter counter = Provider.of<Counter>(context);
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ExampleProviderWidget(),
            ExampleConsumerWidget(),
            ExampleNoListenWidget()
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => counter.increment(),
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

class ExampleProviderWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Counter counter = Provider.of<Counter>(context);

    return Container(
      color: Colors.green,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context):',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

class ExampleConsumerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<Counter>(
      builder: (context, counter, _) {
        return Container(
          color: Colors.blue,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Consumer<Counter>(context):',
                ),
                Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

class ExampleNoListenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    Counter counter = Provider.of<Counter>(context, listen: false);

    return Container(
      color: Colors.red,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context, listen: false):',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () => counter.increment(),
            )
          ],
        ),
      ),
    );
  }
}

7 Answers

That's because, while the widget that called Provider.of with listen:false did not want to rebuild, its parent forced it to.

In your example, when Counter changes, MyHomePage rebuilds and recreate the widget that specified listen:false, which in turn force it to rebuild too.

MyHomePage should specify listen: false too here.

What Remi suggested is a working approach (hack). But I believe the better way in this case will be to remove the call Counter counter = Provider.of(context); from the MyHomePageClass. That call is polluting the scope of your Counter object. As suggested by flutter.io , It is best practice to put your Consumer widgets as deep in the tree as possible. As you did in the ExampleProviderWidget(), ExampleConsumerWidget(),ExampleNoListenWidget(). So make the floatingActionButton a separate widget class and have its own Provider.of(context). And your MyHomePageClass will not have to call Provider.of.

It works for me

I/flutter (12384): MyApp build
I/flutter (12384): MyHomePage build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleConsumerWidget build
I/flutter (12384): ExampleNoListenWidget build
Reloaded 1 of 524 libraries in 792ms.
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build
I/flutter (12384): ExampleProviderWidget build


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

void main() => runApp(MyApp());

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    print('MyApp build');
    return MaterialApp(
      title: 'Provider Demo',
      theme: ThemeData(
        primarySwatch: Colors.amber,
      ),
      home: MyHomePage(title: 'Provider Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;
  Counter _counter = Counter();
  @override
  Widget build(BuildContext context) {
    print('MyHomePage build');
    return ChangeNotifierProvider.value(
      value: _counter,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ExampleProviderWidget(),
              ExampleConsumerWidget(),
              ExampleNoListenWidget()
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => _counter.increment(),
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

class ExampleProviderWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('ExampleProviderWidget build');
    Counter counter = Provider.of<Counter>(context);

    return Container(
      color: Colors.green,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context):',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
    );
  }
}

class ExampleConsumerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('ExampleConsumerWidget build');
    return Consumer<Counter>(
      builder: (context, counter, _) {
        return Container(
          color: Colors.blue,
          child: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'Consumer<Counter>(context):',
                ),
                Text(
                  '${counter.count}',
                  style: Theme.of(context).textTheme.display1,
                ),
              ],
            ),
          ),
        );
      },
    );
  }
}

class ExampleNoListenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('ExampleNoListenWidget build');
    Counter counter = Provider.of<Counter>(context, listen: false);

    return Container(
      color: Colors.red,
      child: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Provider.of<Counter>(context, listen: false):',
            ),
            Text(
              '${counter.count}',
              style: Theme.of(context).textTheme.display1,
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () => counter.increment(),
            )
          ],
        ),
      ),
    );
  }
}

listen: false necessary to be able to call Provider.of inside [State.initState] or the create method of providers.

listen: true, latter value changes will trigger a new [State.build] to widgets, and [State.didChangeDependencies] for [StatefulWidget].

Whenever you say Provider.of<Counter>(context,, you should write , listen: false).

Provider.of<Counter>(context, listen: false);

from the code you have written, the increment method you are using in the onPressed method will not work, if you implement listen: false.

Related