[Flutter]: notifyListeners not working from other screens or popups

Issue

I am using the provider libarary for my state management and everything works fine up until this point. However, I have noticed that when calling notifyListeners from another screen (or a popup like showModalBottomSheet) my UI will not be notified.

The method I am using in the provider looks something like this and adds an item to a ListView:

void addEntry(http.Response response) {
  Group group = Group.fromJson(jsonDecode(response.body));
  groups.add(group);
  notifyListeners();
}

As for the UI the entire Screen is wrapped in a Consumer. Calling this method from a button in the screen works fine and the list is updated. Calling this method from a popup created with showModalBottomSheet does not update the UI.

How do I ensure the UI is notified of the changes?


Update #1:

I have a button that calls showModalBottomSheet like this:

showModalBottomSheet<void>(
  ...
  context: context,
  builder: (context) {
    return Padding(
      padding: MediaQuery.of(context).viewInsets,
      child: AddWidget(model: Provider.of<GroupsProvider>(context)),
    );
  },
);

So it creates a popup with a separate widget. That separate widget in turn does this when another button is pressed:

widget.model.createGroup(name, icon);

This method eventually calls addEntry. As I said this works perfectly fine without a popup but does not work at all in the popup :/

Solution

Pop-ups like bottomsheets are most likely opened from the root navigator and hence provides a different context and if your provider is not created above the root Navigator i.e above the MaterialApp, it will cause this issue.
The context that is coming from the builder of the showModalBottomSheet is not where your Provider is created.
There are 2 solutions I can think of:

  1. Just before calling the showModalBottomSheet , you can create a reference to the Provider and then pass that to the AddWidget.
var provider = Provider.of<GroupsProvider>(context, listen:false);

showModalBottomSheet<void>(
  ...
  context: context,
  builder: (context) {
    return Padding(
      padding: MediaQuery.of(context).viewInsets,
      child: AddWidget(model: provider),
    );
  },
);

  1. Use the ChangeNotifierProvider.value and wrap the widget in the builder with it.
showModalBottomSheet<void>(
  ...
  context: context,
  builder: (_) {
    return ChangeNotifierProvider.value(
          value: Provider.of<GroupsProvider>(context, listen:false),
         builder: (context, _) =>
                Padding(
      padding: MediaQuery.of(context).viewInsets,
      child: AddWidget(model: Provider.of<GroupsProvider>(context)),
    );
       );
  },
);

(2) ensures that the widget tree below it will have access to the same Provider instance. For new screens, you can do :

Navigator.push(
  context,
  MaterialPageRoute(
   builder: (_) => ChangeNotifierProvider.value(...),
   ),
);

Looking at the widget inspector in debug mode will help you understand better

Answered By – Calvin Gonsalves

Answer Checked By – David Goodson (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *