How do you update the state of a sibling widget in Flutter?
For example, if I have a rectangle widget, I can change its color from within the widget by calling setState()
and changing the color variable (as the "Turn Green" button does below). But, say I wanted a button outside of the rectangle that would change its color. How do I communicate to the Rectangle that it's time to change its color, and what color to change to?
Here is my sample code. When the user presses the "Turn Blue" button, I'd like the rectangle to turn blue, but I don't have access to its state from the sibling widget.
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Hello Rectangle',
home: Scaffold(
appBar: AppBar(
title: Text('Hello Rectangle'),
),
body: Column(
children: <Widget>[
HelloRectangle(),
FlatButton(
child: Text('Turn Blue!',
style: TextStyle(fontSize: 40.0),
textAlign: TextAlign.center,
),
onPressed: () {
// How to update state of the Rectangle from here?
},
),
]
),
),
),
);
}
class HelloRectangle extends StatefulWidget {
@override
HelloRectangleState createState() {
return new HelloRectangleState();
}
}
class HelloRectangleState extends State<HelloRectangle> {
Color _color;
@override
void initState() {
super.initState();
_color = Colors.red;
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: _color,
height: 400.0,
width: 300.0,
child: Center(
child: FlatButton(
child: Text('Turn Green!',
style: TextStyle(fontSize: 40.0),
textAlign: TextAlign.center,
),
onPressed: () {
// I can update the state from here because I'm inside the widget
setState(() {
_color = Colors.green;
});
},
),
),
),
);
}
}
The rule of thumb is that you can't access the state of any Widget that isn't above you in the hierarchy. So, basically we need to move the state (color) up to an ancestor. Introduce a StatefulWidget that builds the Scaffold or Column and store the rectangle color there. Now the rectangle widget no longer needs to store the color, so can become a stateless widget - and you can pass the color in through the constructor. Both onPressed callbacks can now call a method on the new StatefulWidget which calls setState. (You could pass that method down to the rectangle widget, amongst other ways.)
There are two good introductions to best practise here and here.
Here is basic example how to do it from outside.
import 'package:flutter/material.dart';
void main() {
runApp(
new MyApp(),
);
}
class MyApp extends StatefulWidget {
const MyApp({
Key key,
}) : super(key: key);
@override
MyAppState createState() {
return new MyAppState();
}
}
class MyAppState extends State<MyApp> {
MaterialColor _color = Colors.green;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Hello Rectangle',
home: Scaffold(
appBar: AppBar(
title: Text('Hello Rectangle'),
),
body: Column(children: <Widget>[
HelloRectangle(_color),
FlatButton(
child: Text(
_color == Colors.green ? "Turn Blue" : "Turn Green",
style: TextStyle(fontSize: 40.0),
textAlign: TextAlign.center,
),
onPressed: () {
setState(() {
_color = _color == Colors.green ? Colors.blue : Colors.green;
});
},
),
]),
),
);
}
}
class HelloRectangle extends StatefulWidget {
final Color color;
HelloRectangle(this.color);
@override
HelloRectangleState createState() {
return new HelloRectangleState();
}
}
class HelloRectangleState extends State<HelloRectangle> {
HelloRectangleState();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
color: widget.color,
height: 400.0,
width: 300.0,
),
);
}
}
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