Flutter Provider updates multiple time when changing route

1k Views Asked by At

I use ChangeNotifierProvider of a model, which has a "number" property and a method to set a random value to it. There are 2 routes. In the first one, there is a variable which listens to this property using context.select or context.watch (does not matter) and then used in a Text widget. And there is also a button that calls a method of the model, which sets random value. Initially, if no screens have changed, when this button is pressed, the widget is rebuilt as expected. The problem occurs when pushing to another route and then returning back to the first one. If the button is pressed again, the widget is rebuilt twice, because provider the for some reason is also changed twice. And the more times you switch between screens, the more times the widget is rebuilt when you press the button for setting a random value. Below you can see a screenshot from Dart DevTools with logs. Here I gave a small example, just to illustrate my problem, but in another project, where instead of a simple text widget there is a table with data, the decrease in performance after each screen change becomes more noticeable, because the response to user actions, leading to change in the Model, becomes longer.

class MyModel extends ChangeNotifier {
  int _number = 0;
  int get number => _number;

  void setRandomNumber() {
    print('Set random number');
    _number = Random().nextInt(1000);
    notifyListeners();
  }
}
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider<MyModel>(
      create: (context) => MyModel(),
      child: MaterialApp(
        routes: {
          '/home': (context) => MyHomePage(),
          '/second': (context) => SecondPage(),
        },
        initialRoute: '/home',
      ),
    );
  }
}
class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final number = context.select<MyModel, num>((model) => model.number);
    print('REBUILD');

    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            Text(number.toString()),
            TextButton(
              child: Text('Go to Page 2'),
              onPressed: () {
                print('Go to Page 2');
                Navigator.pushNamed(context, '/second');
              },
            ),
            TextButton(
              child: Text('Set Random Number'),
              onPressed: () => context.read<MyModel>().setRandomNumber(),
            ),
          ],
        ),
      ),
    );
  }
}
class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: TextButton(
        child: Text('Go to Home Page'),
        onPressed: () {
          print('Go to Home Page');
          Navigator.pushNamed(context, '/home');
        },
      ),
    );
  }
}

Console:

flutter: Set random number
flutter: REBUILT

flutter: Go to Page 2
flutter: Go to Home Page

flutter: Set random number
flutter: REBUILT
flutter: REBUILT

Logs in DevTools

enter image description here

1

There are 1 best solutions below

1
On BEST ANSWER

It's because you push without popping. You have a stack of Screen 1, Screen 2, Screen 1. You press the button on Screen 1, it executes the set random number function and it notifies the listeners on both instances of screen 1.

It's generally a good practice not to have duplicate screens in your navigation stack at any time. Packages like auto_route enforce this somewhat, though you can also manage this without the use of a package. Just be diligent and be wary of pushing when popping can lead you to the same screen.