18

I'm currently trying Provider as a state management solution, and I understand that it can't be used inside the initState function.

All examples that I've seen call a method inside a derived ChangeNotifier class upon user action (user clicks a button, for example), but what if I need to call a method when initialising my state?

Motivation: Creating a screen which loads assets (async) and shows progress

An example for the ChangeNotifier class (can't call add from initState):

import 'package:flutter/foundation.dart';

class ProgressData extends ChangeNotifier {
  double _progress = 0;

  double get progress => _progress;

  void add(double dProgress) {
    _progress += dProgress;
    notifyListeners();
  }
}
DAG
  • 6,710
  • 4
  • 39
  • 63
user6097845
  • 1,257
  • 1
  • 15
  • 33

6 Answers6

16

You can call such methods from the constructor of your ChangeNotifier:

class MyNotifier with ChangeNotifier {
  MyNotifier() {
    someMethod();
  }

  void someMethod() {
    // TODO: do something
  }
}
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 3
    If you call notifyListeners() inside the constructor and use the provider in build method, doesn't it cause infinite rebuild? or maybe MyNotifier will instantiate only once, like a singleton? appreciated if explained. – Jalali Shakib Mar 14 '20 at 08:43
  • As I recall, the Class instance of ChangeNNotifier is created once and Provider avoids creating new copies unless absolutely required. It will not cause infinite re-render. Just tested :) – krupesh Anadkat Jun 27 '21 at 18:05
5

Change your code to this


class ProgressData extends ChangeNotifier {
  double _progress = 0;

  double get progress => _progress;

  void add(double dProgress) async {
    // Loading Assets maybe async process with its network call, etc.
    _progress += dProgress;
    notifyListeners();
  }

  ProgressData() {
    add();
  }
}

ejabu
  • 2,998
  • 23
  • 31
5

In initState all the of(context) things don't work correctly, because the widget is not fully wired up with every thing in initState. You can use this code:

Provider.of<ProgressData>(context, listen: false).add(progress);

Or this code:

Future.delayed(Duration.zero).then((_) {
 Provider.of<ProgressData>(context).add(progress)
});
Yahya Uddin
  • 26,997
  • 35
  • 140
  • 231
Fateme Afshari
  • 424
  • 4
  • 9
  • To explain furthur, using `listen: false` is usually done when calling a method in the provider. But if you'd like to read data from the provider instead then keep it out. – enchance Nov 01 '22 at 06:28
1

So an AssetLoader class which reports on its progress will look something like this, I guess:

import 'package:flutter/foundation.dart';

class ProgressData extends ChangeNotifier {
  double _progress = 0;

  ProgressData() {
    _loadFake();
  }

  Future<void> _loadFake() async {
    await _delayed(true, Duration(seconds: 1));
    _add(1.0);
    await _delayed(true, Duration(seconds: 2));
    _add(2.0);
    await _delayed(true, Duration(seconds: 3));
    _add(3.0);
  }

  // progress
  double get progress => _progress;

  // add
  void _add(double dProgress) {
    _progress += dProgress;
    notifyListeners();
  }

  // _delayed
  Future<dynamic> _delayed(dynamic returnVal, Duration duration) {
    return Future.delayed(duration, () => returnVal);
  }
}
user6097845
  • 1,257
  • 1
  • 15
  • 33
0

As Fateme said:

the widget is not fully wired up with everything in initState

Also, you can use something like this in your initState

WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
      Provider.of<ProgressData>(context, listen: false).add(5);
    });

I think it's more standard!

Be aware that you should use the correct context! I mean the context of the Builder!

Navid
  • 901
  • 9
  • 20
0

The problem here lies with the fact that context does not exist yet in initState as extensively explained by the other answers. It doesn't exist because it hasn't yet been made a part of the widget tree.

Calling a method

If you're not assigning any state and only calling a method then initState would be the best place to get this done.

// The key here is the listen: false
Provider.of<MyProvider>(context, listen: false).mymethod();

The code above is allowed by Flutter because it doesn't have to listen for anything. In short, it's a one off. Use it where you only want to do something instead of read/listen to something.

Listening to changes

Alternatively, if you need to listen to changes from Provider then the use of didChangeDependencies would be the best place to do so as context would exist here as in the docs.

This method is also called immediately after initState.

int? myState;

@override
void didChangeDependencies() {
  // No listen: false
  myState = Provider.of<MyProvider>(context).data;
  super.didChangeDependencies();
}

If you've never used didChangeDependencies before, what it does is get called whenever updateShouldNotify() returns true. This in turn lets any widgets that requested an inherited widget in build() respond as needed.

I'd usually use this method in a FutureBuilder to prevent reloading data when data already exists in Provider after switching screens. This way I can just check Provider for myState and skip the preloader (if any) entirely.

Hope this helps.

enchance
  • 29,075
  • 35
  • 87
  • 127