In my app the menu have items with "cut-off" times. Once the cut-off time is reached, the item turns red and becomes disabled with a note stating "Cut-off time missed".
This works fine except that for a user watching the menu when the cutoff time passes the display does not update dynamically. Which in not a major issue for my current app, if somebody wanted to sit and watch the menu and notice that it only updates when you close and re-open the menu, I don't care.
But as a learning exercise it would be good if I could make it so that the menu items gets rebuilt once the time is passed.
I however do not want to turn the large Menu tree into a stateful widget. Doing this with a stateful widget is easy enough and the minimal example below (Based off of the flutter default app) shows the desired effect. There are two fields which are updated periodically (on every second) using a timer which just calls setState every second.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime _markedTime;
// Current Time plus a few seconds....
void _markTheTime() {
setState(() {
_markedTime = DateTime.now().add(Duration(seconds: 15));
});
}
// For the purpose of this test we won't use the intl package.
String hhmmssFromDateTime(DateTime tm) {
if (tm == null) {
return null;
}
return '${zeroPadded(tm.hour)}:${zeroPadded(tm.minute)}:${zeroPadded(
tm.second)}';
}
String zeroPadded(int number) => number.toString().padLeft(2, '0');
String get markedTime => hhmmssFromDateTime(_markedTime);
String get currentTime => hhmmssFromDateTime(DateTime.now());
Timer rebuildTimer;
@override
void initState() {
rebuildTimer = Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_markedTime == null
? Text('Mark the time with the FAB...')
: Text(
'Marked Time: $markedTime',
style: Theme
.of(context)
.textTheme
.bodyText1
.copyWith(
color: DateTime.now().isAfter(_markedTime)
? Colors.red
: Colors.blue),
),
Text(
'Current Time: $currentTime',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _markTheTime,
tooltip: 'Set',
child: Icon(Icons.lock_clock),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I believe that the solution lies in a StatefulBuilder wrapped around the Column, in stead of a stateful Widget. But I don't understand StatefulBuilders even though they look really handy.
Also I wonder whether it would be possible to wrap another one around the FloatingActionButton to say disable the button once it has been tapped until the marked time expires.
I think what I want to achieve ultimately is a way to cause various parts of the widget tree to rebuild independantly of one another. So for example using separate timers, the Fab should rebuild at the time when the timer expires, while another periodic timer should update the "Time display" every second.
Use the timer_builder package. An example:
@override
Widget build(BuildContext context) {
return TimerBuilder.scheduled(
[cutoffTime],
builder: (context) {
...
}
);
}
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