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())
)
);
DartAnd, 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();
}
}
DartThe 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!!'),
)
);
}
DartHaving 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;
};
}
DartAt 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();
DartBecause 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
Leave a Reply