'Flutter + Riverpod: Calling a function in a stateful widget?
I am building a timer that is supposed to be triggered with a button press. The timer is done via a ticker in a stateful widget and the whole setup is very simple. However, my problem is that the button to start the timer is quite far away in the widget tree from the timer itself and I don't know how to trigger the method to start the timer from that button.
I know that I can pass functions as arguments but that wouldn't be practical for this project. Ideally, I would like to use Riverpod to connect the widgets, can that be done? (Sorry if that is a stupid question, I'm quite new to Flutter and Riverpod).
My code for the timer:
class SimpleTimer extends StatefulWidget {
const SimpleTimer({Key? key}) : super(key: key);
@override
_SimpleTimerState createState() => _SimpleTimerState();
}
class _SimpleTimerState extends State<SimpleTimer>
with TickerProviderStateMixin {
late Ticker _ticker;
Duration elapsedTime = Duration.zero;
@override
void initState() {
super.initState();
_ticker = this.createTicker(
(elapsed) => setState(() => elapsedTime = elapsed),
);
}
@override
void dispose() {
super.dispose();
_ticker.dispose();
}
void startTimer() => _ticker.start();
@override
Widget build(BuildContext context) {
return Text(elapsedTime.inSeconds.toString());
}
}
and the code for the button
class TimerStartButton extends StatelessWidget {
const TimerStartButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {}, // how to call startTimer from SimpleTimer here?
child: Text('start timer'),
);
}
}
Solution 1:[1]
Ah, figured it out with the help of the tutorial posted by xion in the comments. In case someone is looking for a solution, the way I approached it was by using a StateProvider that returns either true or false (if the ticker is active or not) and then inside of the timer widget I used a ProviderListener to run a function if that timer changed.
New code for the timer:
class SimpleTimer extends StatefulWidget {
const SimpleTimer({Key? key}) : super(key: key);
@override
_SimpleTimerState createState() => _SimpleTimerState();
}
class _SimpleTimerState extends State<SimpleTimer>
with TickerProviderStateMixin {
late Ticker _ticker;
Duration elapsedTime = Duration.zero;
@override
void initState() {
super.initState();
_ticker = this.createTicker((elapsed) {
setState(() {
elapsedTime = elapsed;
});
});
}
@override
void dispose() {
super.dispose();
_ticker.dispose();
}
void startTicker() => _ticker.start();
@override
Widget build(BuildContext context) {
return Consumer(
builder: (
BuildContext context,
ScopedReader watch,
Widget? child,
) {
return ProviderListener(
onChange: (context, state) => startTicker(),
provider: timerProvider,
child: Text(
elapsedTime.inSeconds.toString(),
),
);
},
);
}
}
New code for the starter button:
class TimerStartButton extends ConsumerWidget {
const TimerStartButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ScopedReader watch) {
return ElevatedButton(
onPressed: () => context.read(timerProvider).state = true,
child: Text('start'),
);
}
}
And now a riverpod StateProvder was added as well:
final timerProvider = StateProvider<bool>((ref) {
return false;
});
Solution 2:[2]
Another option for those here from a search engine is to extend ConsumerStatefulWidget.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Another_coder |
| Solution 2 | Kyle |
