Search code examples
flutterinitializationstaterefreshprovider

When do we initialise a provider in flutter?


I just arrived on a flutter project for a web app, and all developers have a problem using flutter provider for state management.

What is the problem
When you arrive on a screen, the variables of the corresponding provider are initialised by calling a function of the provider. This function calls an api, and sets the variables in the provider.
Problem : This function is called in the build section of the widget. Each time the window is resized, the widget is rebuilt, and the function is called again.

What we want
We want to call an api when the page is first displayed, set variables with the result, and not call the api again when the widget is rebuilt.

What solution ?

  1. We use a push from the first screen to go to the second one. We can call the function of the provider at this moment, to initialise the provider just before the second screen. → But a refresh on the second page will clear the provider variables, and the function to initialise them will not be called again.
  2. We call the function to initialise the provider in the constructor of the second screen. Is it a good pattern ?

Thank you for your help in my new experience with flutter :)


Solution

  • I think you're mixing a couple different issues here:

    1. How do you correctly initialize a provider
    2. How do you call a method on initialization (only once)

    For the first question:

    In your main.dart file you want to do something like this:

    @override
    Widget build(BuildContext context) {
      return MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (context) => SomeProvider()),
          ChangeNotifierProvider(create: (context) => AnotherProvider()),
        ],
        child: YourRootWidget();
      );
    }
    

    Then in a widget (that probably represents a "screen" in your app), you need to do something like this to consume state changes from that provider:

    @override
    Widget build(BuildContext context) {
      return Container(
        child: Consumer<SomeProvider>(
          builder: (context, provider, child) {
            return Text(provider.someState);
          }
        ),
      )
    }
    

    And you need to do something like this to get access to the provider to mutate state:

    @override
    Widget build(BuildContext context) {
      SomeProvider someProvider = Provider.of<SomeProvider>(context, listen: false);
      return Container(
        child: TextButton(
          child: Text('Tap me'),
          onPressed: () async {
            await someProvider.mutateSomeState();
          }
        ),
      )
    }
    

    Regarding the second question... You can (I think) just use the initState() method on a widget to make the call only 1 time. So...

    @override
    void initState() {
      super.initState();
      AnotherProvider anotherProvider = Provider.of<AnotherProvider>(context, listen: false);
      Future.microtask(() {
        anotherProvider.doSomethingElse();
      });
    }
    

    If I'm off on any of that, I'm sorry. That mirrors my implementation and works fine/well.

    A caveat here is that I think RiverPod is likely the place you really want to go (it's maybe easier to work with and has additional features that are helpful, etc.) but I've not migrated to RiverPod yet and do not have that figured out all the way.

    Anyway... Good luck!