Gravid Banner

Using Provider to Share State Between Widgets

Today I needed to share a timer between two widgets. A quick search revealed that the way to go was to use Provider. So, as usual I looked for a good example of how to go about it. I didn’t actually find one, so instead I took a look at the official documentation (https://pub.dev/packages/provider). Unfortunately, as is often the case the readme there is written from the perspective of assuming that you already know what to do. As of today there is more text telling you how it has changed from previous implementations than there is text detailing a simple use case.

My first attempt at implementation gave me the very unhelpful error

Error: Could not find the correct Provider<MyHomePageState> above this Graphic Widget

It turns out that I tried to implement the provider in the widget where my timer was declared – it seemed a logical place to do it. Unfortunately that widget was also the place from which the second widget was created using:

await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ReceivingWidget())
    )
);
Dart

And, as I discovered with the help of this StackOverflow the provider has to be declared in a shared ancestor of both the source of the shared data (Widget A) and the recipient (Widget B), because they don’t share the same context. Clear as mud? A diagram may help.

App (App Context)
  - Widget A (context A inherited from App Context)
  - Widget B (context B inherited from App Context)

Both Widget A and Widget B get their context from App – but Widget B cannot see anything declared in Widget A’s context. So the Provider needs to be created at the App level in order for both A and B to see it.

Armed with that knowledge the first step was to declare a class which will pass the shared time data. The class uses “with ChangeNotifier” to inherit the ability to notify any listeners when the value of the timer (in this case _day) changes.

class Journey with ChangeNotifier{
  int _day = 0;
  int getDay() => _day;

  void incrementDay() {
    _day++;
    notifyListeners();
  }
}
Dart

The next step was to modify the App Build tree to declare the Provider (in this case a ChangeNotifierProvider).

@override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => Journey(),
      child: MaterialApp(
          home: const MyHomePage(title: 'My Home Page Title!!'),
      )
    );
  }
Dart

Having done this, I needed to modify my timer code update the value of _day in the Journey Class. Provider has two methods, read and watch which allow us to access the provider: read gets the provider data without needing the widget to rebuilt, whilst watch forces a rebuild.

  bool _timerRunning = false;
  
  void _toggleTimer(BuildContext context) {
    if (!_timerRunning) {
      _timer = Timer.periodic(const Duration(milliseconds: 200), (Timer timer) {
        // This is where we update the provider and as incrementDay calls
        // notifyListeners lets everything know it's changed
        context.read<Journey>().incrementDay();
      });
      _timerRunning = true;
    }
    else {
      _timer!.cancel();
      _timerRunning = false;
    };
  }
Dart

At this point I made a typo which caused another error and took me a while to spot (sometimes simple differences are too subtle to spot!!)

// Instead of
context.read<Journey>().incrementDay();

// I wrote
context.read()<Journey>().incrementDay();
Dart

Because there were braces before the type this gave the following error:

Tried to call Provider.of<dynamic>. This is likely a mistake and is therefore unsupported

The final step of the puzzle is to read the value of the timer in the receiving widget

@override
  Widget build(BuildContext context) {
    // Currently not assigned to anything as I don't actually care which day it is, 
    // I just need force the widget to rebuild
    context.watch<Journey>().getDay();
    
    return Scaffold(
      // My widget code here...
    );
  }
Dart

Comments

Leave a Reply

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