Provider: ChangeNotifierProxyProvider : redundant updates after updating provider to v4.0

Created on 9 Mar 2020  路  6Comments  路  Source: rrousselGit/provider

Hi Remi,
First of all, I love your package provider :) We've been using it in a big Flutter project from 6 months.

But since we upgraded provider from v3.2 to v4 (now 4.0.4),
we noticed a few drawbacks.

The strangest is :
ChangeNotifierProxyProvider "update" method is called too many times.

I simplified the scenario and here is the code to reproduce the issue :

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// provider v4.0.4
import 'package:provider/single_child_widget.dart';

List<SingleChildWidget> rootProviders = [
// provider v3.2.0
// List<SingleChildCloneableWidget> rootProviders = [
  ChangeNotifierProvider(
    create: (context) => AccountNotifier(),
  ),
];

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

class AccountNotifier extends ChangeNotifier {}

class AdvancedCardViewModel extends ChangeNotifier {
  AccountNotifier _accountNotifier;
  void updateDependencies({AccountNotifier accountNotifier}) {
    _accountNotifier = accountNotifier;
    // Here, call a fetching data from remote method.
    print('fetching data');
  }
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: rootProviders,
      child: MaterialApp(
        title: 'Provider test',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: MyHomePage(title: 'Provider test'),
      ),
    );
  }
}

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

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'Main page. Click on the button aboce to show an advance card.',
            ),
            FlatButton(
              onPressed: () {
                showModalBottomSheet(
                  context: context,
                  isScrollControlled: true,
                  backgroundColor: Colors.transparent,
                  builder: (context) {
                    return ChangeNotifierProxyProvider<AccountNotifier, AdvancedCardViewModel>(
                        create: (context) => AdvancedCardViewModel(),
                        update: (context, accountNotifier, viewModel) {
                          return viewModel
                            ..updateDependencies(
                              accountNotifier: accountNotifier,
                            );
                        },
                        child: Consumer<AdvancedCardViewModel>(
                          builder: (context, myType, child) {
                            return Container(
                              decoration: const BoxDecoration(color: Colors.white),
                              child: Center(
                                child: Column(
                                  mainAxisAlignment: MainAxisAlignment.center,
                                  children: <Widget>[
                                    Text(('Advance card')),
                                  ],
                                ),
                              ),
                            );
                          },
                        ));
                  },
                );
              },
              child: Text('bouton'),
            ),
          ],
        ),
      ),
    );
  }
}

There is a main page with a button, under a root provider containing an AccountNotifier.
On Tap to the button, we want to displayed a detail page, but first we create a viewmodel (AdvancedCardViewModel) with a proxy provider on the AccountNotifier.

On update, the AdvancedCardViewModel get the AccountNotifier provided instance and do some webservice calls with it in order to fetch relevant data.

With provider v3.2, on tap to the button, update is called once (cool, we need the data to be refreshed once).

With provider v4, update is called a loooot of times (as I can see in the stacktrace if I add a breakpoint on "updateDependencies", it is trigger by Consumer.buildWithChild.

So here is my question : what's the problem with provider v4 ? Do I use it wrong ?
I had to add an ugly workaround with a flag to load data once.

Maybe it is related to this issue #371

Thanks for your time

Most helpful comment

This is expected.

The difference between v3 and v4 is that on v4, update is called again when ChangeNotifierProvider rebuilds.

To begin with, it was never safe to do unchecked side-effects inside update. You always needed to wrap it in a condition like so:

void update() {
  if (value != previousValue) {
    previousValue = value;
    doSomeHttpRequest(value);
  }
}

This "somehow" worked before if you didn't do such a check but had only a single dependency that updates rarely.
But that does not mean you were good to go, and could still have issues (just harder to spot).

Now, the reason why you update is called "a loooooot of times" is because of showModalBottomSheet.
This function calls its builder parameters a huge number of times, for whatever reason.

All 6 comments

This is expected.

The difference between v3 and v4 is that on v4, update is called again when ChangeNotifierProvider rebuilds.

To begin with, it was never safe to do unchecked side-effects inside update. You always needed to wrap it in a condition like so:

void update() {
  if (value != previousValue) {
    previousValue = value;
    doSomeHttpRequest(value);
  }
}

This "somehow" worked before if you didn't do such a check but had only a single dependency that updates rarely.
But that does not mean you were good to go, and could still have issues (just harder to spot).

Now, the reason why you update is called "a loooooot of times" is because of showModalBottomSheet.
This function calls its builder parameters a huge number of times, for whatever reason.

Ok, so it is normal and expected.

Merci pour l'exemple, et bonne fin d'aprem

After hours of debugging, i came across this issue and finally solved it. Hope can include this sample in the docs

After hours of debugging, i came across this issue and finally solved it. Hope can include this sample in the docs

@liongkj
How did you solve it?

This is expected.

The difference between v3 and v4 is that on v4, update is called again when ChangeNotifierProvider rebuilds.

To begin with, it was never safe to do unchecked side-effects inside update. You always needed to wrap it in a condition like so:

void update() {
  if (value != previousValue) {
    previousValue = value;
    doSomeHttpRequest(value);
  }
}

This "somehow" worked before if you didn't do such a check but had only a single dependency that updates rarely.
But that does not mean you were good to go, and could still have issues (just harder to spot).

Now, the reason why you update is called "a loooooot of times" is because of showModalBottomSheet.
This function calls its builder parameters a huge number of times, for whatever reason.

Refer the example the rroussel gave, need to have a if check whether state should be updated

This is expected.
The difference between v3 and v4 is that on v4, update is called again when ChangeNotifierProvider rebuilds.
To begin with, it was never safe to do unchecked side-effects inside update. You always needed to wrap it in a condition like so:

void update() {
  if (value != previousValue) {
    previousValue = value;
    doSomeHttpRequest(value);
  }
}

This "somehow" worked before if you didn't do such a check but had only a single dependency that updates rarely.
But that does not mean you were good to go, and could still have issues (just harder to spot).
Now, the reason why you update is called "a loooooot of times" is because of showModalBottomSheet.
This function calls its builder parameters a huge number of times, for whatever reason.

Refer the example the rroussel gave, need to have a if check whether state should be updated

Yeah, I got it. I thought there was more to it because in my case the widget is still rebuilding when using a modal dialog. Thanks anyway.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FaizanKamal7 picture FaizanKamal7  路  6Comments

usherwalce picture usherwalce  路  5Comments

guopeng1994 picture guopeng1994  路  4Comments

dikir picture dikir  路  5Comments

stocksp picture stocksp  路  5Comments