Imagine one of those peaceful Saturday mornings with your kids:
- “I did the dishes three times in a row this week!” (kid)
- “Wow, breakfast only, doesn’t count! It’s definitely your turn!” (Other kid)
Don’t know how much time we spent discussing “Who’s on Duty” doing the dishes this morning. No question at least twice the time it would have taken simply doing it. Joining the discussion seemed no option at that time…way too risky for a sleepy adult to enter the ring.
Since I wanted to support one of the kids with its effort to write a Flutter app anyway, that was the nudge I needed to dive into the matter…
The Development Setup
Not much to add to the official documentation here. Just a few pointers to short-circuit the search: Tools & techniques
A decent version of Android Studio with two additional plugins for Flutter and Dart was enough to get me started.
Tip: Run
flutter doctor
to check the setup for completeness.
Visit the official “Run flutter doctor” for additional information.
Who’s on Duty?
First, we created a seed for our application by simply running through the New Flutter Project…
wizard.
The main obstacle here is that you’ll need to know the Flutter SDK path on your disk. 🤔
Jump into lib/main.dart
where you’ll find the generated Flutter app.
Note: For better readability, we removed the non-essential comments and explanations.
From 🐛 caterpillar to butterfly 🦋
As mentioned we removed the comments and additionally renamed the generated class to match it’s new purpose to OnDutyApp
:
class OnDutyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'On Duty App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: OnDutyPage(),
);
}
}
Another rename (and minor cleanup): MyHomePage
to OnDutyPage
.
We removed the constructor parameter title
and the corresponding field.
class OnDutyPage extends StatefulWidget {
@override
OnDutyPageState createState() => OnDutyPageState();
}
With _MyHomePageState
we are slowly getting to the core of the small app.
The state _counter
is replaced with onDuty
where we keep the list of those who will be on duty today.
class OnDutyPageState extends State<OnDutyPage> {
List<String> slots = ['Breakfast', 'Lunch', 'Supper'];
List<String> candidates = ['Huey', 'Dewey', 'Louie'];
List<String> onDuty = List<String>.filled(3, '');
void _refreshOnDutyPage() {
// …
}
@override
Widget build(BuildContext context) {
// …
}
}
The main layout based on the Scaffold
widget, in general, is kept the same.
One single line with text and counter:
<Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
]
is replaced by the list of people who are on duty today backed by the variable onDuty
and slots
:
List<Widget> generateSlots(BuildContext context, List<String> slots) {
List<Widget> widgets = [];
for (var i = 0; i < slots.length; i++) {
widgets.add(Text(slots[i]));
widgets.add(Padding(
padding: EdgeInsets.all(12.0),
child: Text(
'${onDuty[i]}',
style: Theme.of(context).textTheme.headline4,
)));
}
return widgets;
}
Since it was already there we kept the floating action button, gave it a new look and wired it to the function refreshOnDutyPage
:
floatingActionButton: FloatingActionButton(
onPressed: _refreshOnDutyPage,
tooltip: 'Refresh',
child: Icon(Icons.autorenew),
),
🎉 First steps towards relaxed Saturdays
🪄 The Magic Bits
Let's add the ~~independent, unblamable supervisor~~ scapegoat part to the app:
void _refreshOnDutyPage() {
setState(() {
var anchor = DateTime.parse("1973-05-08");
var difference = anchor.difference(DateTime.now());
int offset = difference.inDays % slots.length;
for (var i = 0; i < slots.length; i++) {
onDuty[i] =
candidates[(difference.inDays + i + offset) % candidates.length];
}
});
}
Et Voilá - it's Dewey!
And all family Saturdays were peacefully and happily ever after... 😴
Photo by Hidde Rensink on Unsplash