Flutter: Update state of sibling widget - flutter

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,
),
);
}
}

Related

How can i make a moveable overlay?

I want to show a minimize moveable calling screen in top of the app
I tried with stack it does not meet my expectation
#Raiyan, you have to use picture-in-picture concept to implement such floating child.
In flutter, multiple plugins are there, that we can use for the, some are as follows:
https://pub.dev/packages/pip_view
https://pub.dev/packages/floating
https://pub.dev/packages/easy_pip
floating package will fit in your case, it provides picture in Picture mode management for Flutter.
Sadly the gif is not working... But by on taping and draging on the green window will make the green window move.
Try this:
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return OverlayWindow(
overlayChild: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Overlay Window",
style: TextStyle(fontSize: 20),
),
Icon(
Icons.android,
size: 80,
),
],
),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
class OverlayWindow extends StatefulWidget {
const OverlayWindow(
{Key? key, required this.overlayChild, required this.child})
: super(key: key);
final Widget overlayChild;
final Widget child;
#override
State<OverlayWindow> createState() => _OverlayWindowState();
}
class _OverlayWindowState extends State<OverlayWindow> {
double _top = 0;
double _left = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
widget.child,
Positioned(
top: _top,
left: _left,
child: GestureDetector(
onPanUpdate: (details) {
setState(() {
_top = max(0, _top + details.delta.dy);
_left = max(0, _left + details.delta.dx);
});
},
child: Container(
height: 300,
width: 200,
color: Colors.green,
child: widget.overlayChild,
),
),
)
],
);
}
}
More about things like that, you can find here:
https://docs.flutter.dev/development/ui/advanced/gestures

flutter build only a single widget

I have two widgets in a column. One is Text and second is TextButton. What i want that if i click on button then the Text widget rebuild only not the whole page.
I am new to flutter how can i achieve this? If i convert this to a statful widget and call setState method then whole page will be rebuild. but i want to know any trick to do rebuild only a single widget out of whole page.
class Page3 extends StatelessWidget {
Color color = Colors.red;
changeColor() {
// do something to rebuild only 1st column Text not the whole page
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: color),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
Please refer to below code
ValueListenableBuilder widget. It is an amazing widget. It builds the widget every time the valueListenable value changes. Its values remain synced with there listeners i.e. whenever the values change the ValueListenable listen to it. It updates the UI without using setState() or any other state management technique.
In Dart, a ValueNotifier is a special type of class that extends a ChangeNotifer . ... It can be an int , a String , a bool or your own data type. Using a ValueNotifier improves the performance of Flutter app as it can help to reduce the number times a widget gets rebuilt.
ValueListenableBuilder will listen for changes to a value notifier and automatically rebuild its children when the value changes.
For more info refer to this link description
Solution 1
class Page3 extends StatelessWidget {
Color color = Colors.red;
final ValueNotifier<bool> updateColor = ValueNotifier(false);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
color = changedColor;
updateColor.value = !updateColor.value;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<bool>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: color),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Solution 2
In ValueListenable we pass our created ValueNotifier variable whose changes will be notified and in builder we will return a widget that will be reflected every time when the value of ValueNotifier will be changed.
class Page3 extends StatelessWidget {
// Color color = Colors.red;
final ValueNotifier<Color> updateColor = ValueNotifier(Colors.red);
changeColor(Color changedColor) {
// do something to rebuild only 1st column Text not the whole page
updateColor.value = changedColor;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
ValueListenableBuilder<Color>(
valueListenable: updateColor,
builder: (context, val, child) {
return Text(
'Title',
style: TextStyle(color: val),
);
}),
//Second widget
TextButton(
onPressed: () => changeColor(Colors.purple),
child: Text('change color of title'),
)
],
));
}
}
Here's the code of what you need to do
class Demo extends StatefulWidget {
const Demo({Key? key}) : super(key: key);
#override
State<Demo> createState() => _DemoState();
}
class _DemoState extends State<Demo> {
var isTextChanged = false;
Void changeColor() {
setState(() {
isTextChanged = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Page3'),
),
body: Column(
children: [
//First widget
Text(
'Title',
style: TextStyle(color: isTextChanged ? Colors.red : Colors.black),
),
//Second widget
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
],
));
}
}
setStatefunction can not be called inside StatelessWidget widget. if you want to rebuild the widget tree, you have to convert it to StatefulWidget.
This is what you can do.
class Page3 extends StatefulWidget {
const Page3();
#override
_Page3State createState() => _Page3State();
}
class _Page3State extends State<Page3> {
Color color = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
Text(
'Title',
style: TextStyle(color: color),
),
TextButton(
onPressed: () => changeColor(),
child: Text('change color of title'),
)
]),
);
}
changeColor() {
setState(() {
color = Colors.green;
});
}
}
If you want to rebuild the Text widget without rebuilding the whole Page3 then you need to got for 'state management' solution.
Try below code hope its help to you. you must used StateFulWidget for that
Create one bool variable
bool isButtonPressed = true;
Your widgets:
Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Title',
style: isButtonPressed
? TextStyle(
color: Colors.black,
fontSize: 20,
)
: TextStyle(
color: Colors.green,
fontSize: 20,
),
),
),
TextButton(
child: new Text('Change color'),
onPressed: () {
setState(() {
isButtonPressed = !isButtonPressed;
});
},
),
],
),
Your Screen without button pressed:
Your Screen with button pressed:
You need to understand how setState works.
Lets assume you have a class named HomeScreen, within the homescreen you are overriding the build method to build your own widgets.
Widget build(BuildContext context){
return Column(
children:<Widget> [
FirstTextWidget();
SecondTextWidget();
ThirdTextWidget(),
])
}
when you call SetState function within that "homesceeen" class, the homescreen class itself calls the build method again and all of componenets you have returned within build function get re-rendered. Every text within homescreen class gets rerendered.
So whats the solution?
The Solution is separate your stateful widget with different class so that only those widgets gets rerendered when needed not whole. I will prefer you to use State Management plugin like Provider, bloc etc.

How to change the Value of a Widget in Flutter through dart

i would like to know o can i change the value of a child widget in flutter if a certain condition is met, for example the color of an icon in the trailing
Here is some pseudo-code:
if(condition){
trailing Icon(Icons.favorite).color = Colors.red[500]
}else{
trailing = Icon(Icons.Favorite).color = Colors.blue[300]
}
Thank you.
you wanna something like this?
if yes, try this code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool colorIndex = true;
void _changeColor(val) {
setState(() {
this.colorIndex = val;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
home: Scaffold(
appBar: AppBar(
title: Text('Welcome to Flutter'),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_changeColor(!colorIndex);
},
child: Icon(Icons.touch_app),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(50.0),
child: Card(
child: ListTile(
title: Text('Click FAB to change color'),
trailing: Icon(
Icons.favorite,
color: colorIndex ? Colors.red[500] : Colors.blue[300],
),
),
),
),
),
),
);
}
}
You can change anything under any condition you define. The most simple example is using setState to update a value that can be inspected during build. This value could change under any condition you like. Calling setState will trigger the UI to rebuild (calls the build method).
Here is a Widget. It displays the text "Hello, World!" in the center of the screen. The AppBar at the top has a leading IconButton Widget. When the IconButton is pressed, it will toggle the color of the "Hello, World!" text. It does this by updating the state of Widget and toggling the value of the blue variable. The condition is: if (blue) {} or "if blue is equal to true then change the color." During the build of the UI, the code inspects the value of blue and determines what TextStyle to apply to the "Hello, World!" text.
import 'package:flutter/material.dart';
class ColorChangeWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => _ColorChangeWidgetState();
}
class _ColorChangeWidgetState extends State<ColorChangeWidget> {
bool blue = false;
#override
Widget build(BuildContext context) {
TextStyle style = TextStyle(color: Colors.black);
if (blue) {
style = TextStyle(color: Colors.blue);
}
return Scaffold(
appBar: AppBar(
title: Text("Test"),
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
blue = !blue;
});
},
),
),
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(8.0),
child: Text("Hello, World!", style: style)
)
);
}
}

Flutter: CustomPainter paint method gets called several times instead of only once

I have a simple app that draws via a CustomPainter a red or green circle on a canvas, depending on which button is pressed in the AppBar:
The class ColorCircle extends CustomPainter and is responsible for drawing the colored circle:
class ColorCircle extends CustomPainter {
MaterialColor myColor;
ColorCircle({#required this.myColor});
#override
void paint(Canvas canvas, Size size) {
debugPrint('ColorCircle.paint, ${DateTime.now()}');
final paint = Paint()..color = myColor;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 100, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
The drawing of the different colors works fine, but when I click (only once!) or hover over one of the buttons, the paint method gets called several times:
Further implementation details:
I use a StatefulWidget for storing the actualColor. In the build method actualColor is passed to the ColorCircle constructor:
class _MyHomePageState extends State<MyHomePage> {
MaterialColor actualColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
OutlinedButton(
onPressed: () => setState(() => actualColor = Colors.red),
child: Text('RedCircle'),
),
OutlinedButton(
onPressed: () => setState(() => actualColor = Colors.green),
child: Text('GreenCircle'),
),
],
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: ColorCircle(myColor: actualColor),
),
),
);
}
}
The complete source code with a running example can be found here: CustonPainter Demo
So why is paint called several times instead of only once? (And how could you implement it so that paint is called only once?).
All you need to do is to warp the CustomPaint with RepaintBoundary
Center(
child: RepaintBoundary(
child: CustomPaint(
size: Size(300, 300),
painter: ColorCircle(myColor: actualColor),
),
),
By default CustomPainter is in the same layer as every other widget on the same screen so it's paint method will get called if any other widget on the same screen repaint.
To fix this we can isolate the CustomPainter with RepaintBoundary so any repainting outside this RepaintBoundary wont effect it, or we can fix it by warping other widgets that would repaint with RepaintBoundary so they won't effect any other widgets (including the CustomPainter widget) when they get repaint, however it's better to just warp the CustomPainter with the RepaintBoundary instead of warping multiple widgets with RepaintBoundary since it's costly and sometimes have no effect.
You can get a better view and understanding of this by enabling Highlight repaints in the DevTools.
A poor solution might be to add a RepaintBoundary around the hover Widgets:
class _MyHomePageState extends State<MyHomePage> {
MaterialColor actualColor = Colors.red;
#override
Widget build(BuildContext context) {
print('Rebuilding with $actualColor');
return Scaffold(
appBar: AppBar(
title: Text('CustomPainter Demo'),
actions: <Widget>[
RepaintBoundary(
child: OutlinedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
onPressed: () {
setState(() => actualColor = Colors.red);
},
child: Text('RedCircle')),
),
RepaintBoundary(
child: OutlinedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
onPressed: () {
setState(() => actualColor = Colors.green);
},
child: Text('GreenCircle')),
),
],
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: ColorCircle(myColor: actualColor),
),
),
);
}
}
And then, to properly define the shouldRepaint method of the ColorCircle (currently returning false):
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return (oldDelegate as ColorCircle).myColor != myColor;
}
This seems to be a really poor solution. I would be interested to know of a better, more sustainable answer.
Full source code with RepaintBoundary workaround
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'CustomPainter Demo',
home: MyHomePage(),
);
}
}
class ColorCirle extends CustomPainter {
MaterialColor myColor;
ColorCirle({#required this.myColor});
#override
void paint(Canvas canvas, Size size) {
debugPrint('ColorCircle.paint, ${DateTime.now()}');
final paint = Paint()..color = myColor;
canvas.drawCircle(Offset(size.width / 2, size.height / 2), 100, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return (oldDelegate as ColorCirle).myColor != myColor;
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
MaterialColor actualColor = Colors.red;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('CustomPainter Demo'),
actions: <Widget>[
RepaintBoundary(
child: OutlinedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
onPressed: () {
setState(() => actualColor = Colors.red);
},
child: Text('RedCircle')),
),
RepaintBoundary(
child: OutlinedButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(Colors.black)),
onPressed: () {
setState(() => actualColor = Colors.green);
},
child: Text('GreenCircle')),
),
],
),
body: Center(
child: CustomPaint(
size: Size(300, 300),
painter: ColorCirle(myColor: actualColor),
),
),
);
}
}

How to change ListTile background color when tapped?

I've searched high and low, but cannot find a way to change the background color on a ListTile, for example when it is tapped by the user.
Does anyone have a solution to what seems like a common use case?
To change the background color of a ListTile, you can simply wrap it in a Container and change its color attribute. Afterwards you can change the color, when onTap of the ListTile is triggered.
Demo:
Demo Source:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: CustomTile()
),
),
);
}
}
class CustomTile extends StatefulWidget {
#override
CustomTileState createState() => CustomTileState();
}
class CustomTileState extends State<CustomTile> {
Color color;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
#override
Widget build(BuildContext context) {
return Container(
color: color,
child: ListTile(
title: Text('Title'),
subtitle: Text('Subtitle'),
onTap: () {
setState(() {
color = Colors.lightBlueAccent;
});
},
),
);
}
}
As You Haven't Describes your Use Case or shared any Code i have shared sample code that Change Listile Color onTap()
class Screen1 extends StatefulWidget {
#override
Screen1State createState() {
return new Screen1State();
}
}
class Screen1State extends State<Screen1> {
bool _color;
#override
void initState() {
super.initState();
_color = true;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Card(
color: _color ? Colors.deepOrangeAccent : Colors.purpleAccent,
child: ListTile(
onTap: () {
setState(() {
_color = !_color;
});
},
title: Text(
'Title',
style: TextStyle(color: Colors.white),
),
subtitle: Text(
'Subtitle',
style: TextStyle(color: Colors.white),
),
),
),
));
}
}
If you just want a quick way to do it, without much code and customization, you can wrap it with RaisedButton like this :
RaisedButton(
color: ...
child: ListTile(
color: Colors.transparent,
...
)
)
You can also use many properties like elevation, highlight color, focus color and much more.