I have a dice-rolling MaterialApp
. When the floatingButton
is pressed I want to trigger a "dice-roll" on the children. I'm trying to use the InheritedWidget, but most examples I've seen appear to do the opposite, trigger a parent change from the child.
The Stateless Parent:
class DiceApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Center(
child: Row(
children: <Widget>[
RollTrigger(
roller: Roller(),
child: Die(),
),
RollTrigger(
roller: Roller(),
child: Die(),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.casino),
onPressed: () {
// Trigger the dice roll on each
},
),
),
);
}
}
}
The InheritedWidget
:
class RollTrigger extends InheritedWidget {
RollTrigger({this.roller, this.child});
final roller;
final Widget child;
static RollTrigger of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<RollTrigger>();
}
@override
bool updateShouldNotify(RollTrigger oldWidget) => true;
}
I'm trying using a Roller
class to trigger the roll action:
class Roller {
bool rolling = false;
void roll(_DieState die) {
die.roll();
}
}
And finally the Stateful Die:
class Die extends StatefulWidget {
@override
_DieState createState() => _DieState();
}
class _DieState extends State<Die> {
int value = 0;
final _random = new Random();
roll() {
this.value = 1 + _random.nextInt(6 - 1);
}
_DieState();
@override
Widget build(BuildContext context) {
var roller = RollTrigger.of(context).roller;
return Text(this.value.toString());
}
}
This seems like it should be simpler, but I'm tying myself in knots here.
Edit: I'm updating by putting the Roller at the top level per suggestion. I'm still not sure how to trigger a rebuild for the bottom widget:
final Roller roller = Roller();
...
RollTrigger(
roller: this.roller,
child: Die(),
)
And then I put the roll method in the Roller:
class Roller {
int value = 0;
final _random = new Random();
void roll() {
this.value = 1 + _random.nextInt(6 - 1);
print(this.value);
}
}
Assign the value from the Roller
:
class _DieState extends State<Die> {
@override
Widget build(BuildContext context) {
final roller = RollTrigger.of(context).roller;
return Text(roller.value.toString());
}
}
And finally, call the roll
method at the top level:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.casino),
onPressed: () {
this.roller.roll();
},
),
This updates the Roller
's value, but never changes to value of the _DieState... which is the problem I'm trying to solve. There are a lot of interesting patterns out there, but I think what I'm struggling with is the basic implementation.
This seems like it should be simpler, but I'm tying myself in knots here.
Yes. It should be much simpler!
You need to take advantage of the observer design pattern. In Flutter, an observable is a ChangeNotifier
.
Package provider will help a great deal. All the widget classes you create can be StatelessWidget
.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Die with ChangeNotifier {
//
// _value can be one of null, 1, 2, 3, 4, 5, 6
// A value of null signifies the die is currently rolling.
//
int _value = 1;
final Random _random = Random();
int get value {
return this._value;
}
Future<void> roll() async {
if (this._value == null) {
// already rolling
return;
}
this._value = null;
this.notifyListeners();
await Future.delayed(Duration(seconds: 1));
this._value = 1 + this._random.nextInt(6);
this.notifyListeners();
}
}
// ---------------------------------------------------------
void main() {
return runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(final BuildContext context) {
return MaterialApp(
title: 'Die Rolling App',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(final BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<Die>(
create: (final BuildContext context) {
return Die();
},
),
],
child: Scaffold(
appBar: AppBar(title: const Text("Die Rolling Game")),
body: Center(child: DieDisplay()),
floatingActionButton: RollFAB(),
),
);
}
}
class DieDisplay extends StatelessWidget {
@override
Widget build(final BuildContext context) {
return Consumer<Die>(
builder: (final BuildContext context, final Die die, final Widget child) {
return Text((die.value == null) ? 'rolling' : die.value.toString());
},
);
}
}
class RollFAB extends StatelessWidget {
@override
Widget build(final BuildContext context) {
return FloatingActionButton(
onPressed: () {
final Die die = Provider.of<Die>(context, listen: false);
die.roll();
},
tooltip: 'Roll',
child: const Icon(Icons.casino),
);
}
}