I have a button that displays a SnackBar (or toast) before moving to the next page. I have a countdown and after 5 seconds I push Page2.
RaisedButton(
onPressed: () {
_startTimer();
final snackBar = SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(
'Prepare yourself to start in ${widget._current.toString()}!'), // doesn't work here
duration: new Duration(seconds: widget._start),
action: SnackBarAction(
label: widget._current.toString(), // and neither does here
onPressed: () {
// Some code to undo the change.
},
),
);
Scaffold.of(context).showSnackBar(snackBar);
},
child: Text(
"I'm ready",
style: TextStyle(fontSize: 20),
),
),
Nothing to see on the countdown but I'll paste it just in case:
void _startTimer() {
CountdownTimer countDownTimer = new CountdownTimer(
new Duration(seconds: widget._start),
new Duration(seconds: 1),
);
var sub = countDownTimer.listen(null);
sub.onData((duration) {
setState(() {
widget._current = widget._start - duration.elapsed.inSeconds;
});
});
sub.onDone(() {
print("Done");
sub.cancel();
});
}
So if I display the countdown somewhere else (inside a Text for example) it works but it seems that the SnackBar doesn't change its contain, it always get the max number of the countdown.
SnackBar content can be updated/rebuilt while it's visible by making its content: widget dynamic.
In the code sample below we're using a ValueNotifier and ValueListenableBuilder to dynamically rebuild the content of the SnackBar whenever ValueNotifier is given a new value.
(There are many ways to to maintain state values & rebuild widgets when it changes, such as RiverPod, GetX, StreamBuilder, etc. This example uses the Flutter native ValueListenableBuilder.)
When running this Flutter page, click the FAB to show the SnackBar, then click on the center text to update the SnackBar's content (multiple times if you like).
Use SnackBarUpdateExample() widget in your MaterialApp home: argument to try this example in an emulator or device.
class SimpleCount {
int count = 0;
}
class SnackBarUpdateExample extends StatelessWidget {
static const _initText = 'Initial text here';
/// This can be "listened" for changes to trigger rebuilds
final ValueNotifier<String> snackMsg = ValueNotifier(_initText);
final SimpleCount sc = SimpleCount();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SnackBar Update'),
),
/// ↓ Nested Scaffold not necessary, just prevents FAB being pushed up by SnackBar
body: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Click FAB to show SnackBar'),
SizedBox(height: 20,),
Text('Then...'),
SizedBox(height: 20,),
InkWell(
child: Text('Click → here ← to update SnackBar'),
onTap: () {
sc.count++;
snackMsg.value = "Hey! It changed! ${sc.count}";
},
), /// When snackMsg.value changes, the ValueListenableBuilder
/// watching this value, will call its builder function again,
/// and update its SnackBar content widget
],
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: () => _showSnackBar(context),
),
),
);
}
void _showSnackBar(BuildContext context) {
var _controller = ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: SnackContent(snackMsg))
);
/// This resets the snackBar content when it's dismissed
_controller.closed.whenComplete(() {
snackMsg.value = _initText;
sc.count = 0;
});
}
}
/// The ValueListenableBuilder rebuilds whenever [snackMsg] changes.
class SnackContent extends StatelessWidget {
final ValueNotifier<String> snackMsg;
SnackContent(this.snackMsg);
@override
Widget build(BuildContext context) {
/// ValueListenableBuilder rebuilds whenever snackMsg value changes.
/// i.e. this "listens" to changes of ValueNotifier "snackMsg".
/// "msg" in builder below is the value of "snackMsg" ValueNotifier.
/// We don't use the other builder args for this example so they are
/// set to _ & __ just for readability.
return ValueListenableBuilder<String>(
valueListenable: snackMsg,
builder: (_, msg, __) => Text(msg));
}
}
you need to implement a custom widget with countdown logic in side for the content field of snackbar, like this:
class TextWithCountdown extends StatefulWidget {
final String text;
final int countValue;
final VoidCallback? onCountDown;
const TextWithCountdown({
Key? key,
required this.text,
required this.countValue,
this.onCountDown,
}) : super(key: key);
@override
_TextWithCountdownState createState() => _TextWithCountdownState();
}
class _TextWithCountdownState extends State<TextWithCountdown> {
late int count = widget.countValue;
late Timer timer;
@override
void initState() {
super.initState();
timer = Timer.periodic(Duration(seconds: 1), _timerHandle);
}
@override
void dispose() {
timer.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
child: Text("[$count] " + widget.text),
);
}
void _timerHandle(Timer timer) {
setState(() {
count -= 1;
});
if (count <= 0) {
timer.cancel();
widget.onCountDown?.call();
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With