change variables with setState in Flutter - flutter

I have an issue with setState() in Flutter. I just write a simple program that have a container and a button , the color of container is global variable mycolor and i change it in on_pressed function of button with setState but its doesn't change.
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: _Home(),));
Color bgColor = Colors.red;
class _Home extends StatefulWidget {
#override
__HomeState createState() => __HomeState();
}
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget()
],
);
}
}
class SecondWidget extends StatefulWidget {
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Change color"),
onPressed: (){
setState(() {
bgColor = Colors.green;
});
},
);
}
}
image of my program

You are calling setState in _SecondWidgetState not in __HomeState, so only SecondWidget redraws and it does not depend on bgColor.
What you can do here: the easiest option would be to pass a callback function from __HomeState to SecondWidget, which will call setState inside __HomeState.
Example code:
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget(callSetState);
],
);
}
void callSetState() {
setState((){}); // it can be called without parameters. It will redraw based on changes done in _SecondWidgetState
}
}
class SecondWidget extends StatefulWidget {
final Function onClick;
SecondWidget(this.onClick);
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Change color"),
onPressed: (){
bgColor = Colors.green;
widget.onClick();
},
);
}
}
This is simple solution for two widgets, but you will have problems if you will try to manage state on larger scale. I recommend you to read articles about state management in flutter. This one can be a good start.

You need to pass that variable to your sibling widget SecondWidget().
First you declare it on your SecondWidget like this:
class SecondWidget extends StatefulWidget {
Color backgroundColor;
SecondWidget({Key key, #required this.backgroundColor}) : super(key: key);
#override
_SecondWidgetState createState() => _SecondWidgetState();
}
You need to pass that color from _HomeState to SecondWidget, you do it like this:
class __HomeState extends State<_Home> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
//First Widget
Container(
width: 200,
height: 200,
color: bgColor,
),
//Second Widget
SecondWidget(backgroundColor: bgColor) // Here you pass that color
],
);
}
}
Then on your SecondWidgetState, you can update your other widget color using setState(), like this:
setState(() {
widget.backgroundColor = Colors.blue;
});
Hope this helps fix your issue.

Related

Flutter setState function doesn't work when used to change class member

i have the following codes,
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(widget.text),
);
}
}
This is my custom widget,
class _MainState extends State<Main> {
var n = mWidget(text: "Hi");
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
n,
ElevatedButton(
onPressed: () {
setState(() {
n.text = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
And this is the code in the main.dart file.
The problem is that pressing the button doesn't change the output on the screen unless a hot reload even though I am calling the setState function.
I wonder why is that.
Thanks in advance!
You made a couple of mistakes in this!
In your code, you made a widget named mWidget and created an instance of it, it is not the right approach to access any widget using an instance, as state of instances cannot be updated.
You are using the state of mWidget outside of its scope, where it is not accessible.
You can use keys to achieve what you want. (It is not advisable to use this for large-scale project)
Here is a small code which can help you to achieve the functionality you want.
class mWidget extends StatefulWidget {
mWidget({Key? key, required this.text}) : super(key: key);
String text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
String text = "";
#override
void initState() {
text = widget.text;
super.initState();
}
void updateValue(String newData) {
setState(() {
text = newData;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Text(text),
);
}
}
class _Main extends StatefulWidget {
const _Main({Key? key}) : super(key: key);
#override
State<_Main> createState() => _MainState();
}
class _MainState extends State<_Main> {
GlobalKey<_mWidgetState> _mWidgetStateKey = GlobalKey(); // This is the key declaration of _mWidgetState type
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: "Hi", key: _mWidgetStateKey),
ElevatedButton(
onPressed: () =>
_mWidgetStateKey.currentState!.updateValue("Hello"), // Calling the method of _mWidgetState class.
child: Text("Click me"),
),
],
),
);
}
}
You can reinitialize the n on easy approach like
n = mWidget(text: "Hello");
Or use state-management property like riverpod/bloc. callback method may also help. I am using ValueNotifier, you dont need to make theses statefulWidget
class Main extends StatefulWidget {
const Main({super.key});
#override
State<Main> createState() => _MainState();
}
class _MainState extends State<Main> {
final ValueNotifier textNotifier = ValueNotifier('Hi');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
mWidget(text: textNotifier),
ElevatedButton(
onPressed: () {
setState(() {
textNotifier.value = "Hello";
});
},
child: Text("Click me"),
),
],
),
);
}
}
class mWidget extends StatefulWidget {
mWidget({super.key, required this.text});
ValueNotifier text;
#override
State<mWidget> createState() => _mWidgetState();
}
class _mWidgetState extends State<mWidget> {
#override
Widget build(BuildContext context) {
return Center(
child: ValueListenableBuilder(
valueListenable: widget.text,
builder: (context, value, child) => Text(value),
));
}
}

Flutter setState() not refreshing widget when called from stateful child

I have two stateful widgets: ParentWidget and ChildWidget.
The ChildWidget has a gesture detector wrapping a container and text. When I call onTap the state of the Parent (status increased by 1) is updated properly but setState() is not refreshing the UI.
I tried everything: global keys, inherited widget but nothing works.
Interestingly if I change the ChildWidget to a stateless one then everything start working. Any ideas would be super helpful.
pk
Here is the code:
import 'package:flutter/material.dart';
import 'package:hexcolor/hexcolor.dart';
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int status = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Hexcolor('#1c486d'),
title: Text(
'Test',
),
),
body: ChildWidget(
child: GestureDetector(
onTap: () {
status = status + 1;
setState(() {}); // this is the problematic piece of code.
},
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: Text('PARENT:' + status.toString()),
),
),
),
);
}
}
class ChildWidget extends StatefulWidget {
final Widget child;
ChildWidget({this.child});
#override
_ChildWidgetState createState() => _ChildWidgetState(child);
}
class _ChildWidgetState extends State<ChildWidget> {
Widget child;
_ChildWidgetState(this.child);
#override
Widget build(BuildContext context) {
return child;
}
}
You can pass the parent's status to the ChildWidget so that when the parent's status changes, the ChildWidget's state changes and its build method be called.
body: GestureDetector(
onTap: () {
setState(() {
status = status + 1;
});
},
child: ChildWidget(status: status),
),
);
}
}
class ChildWidget extends StatefulWidget {
final int status;
ChildWidget({this.status});
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
_ChildWidgetState();
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blue,
width: 100,
height: 100,
child: Text('PARENT:' + widget.status.toString()),
);
}
}
Try not making the child final
class ChildWidget extends StatefulWidget {
Widget child;
ChildWidget({this.child});
#override
_ChildWidgetState createState() => _ChildWidgetState(child);
}

Flutter: Where I can add Text()

I want to tap on the screen, and change background color to random color. It works fine, but I want to add Text on the center of screen, help me please.
Maybe someone have ideas, Thank you!
I try to add multiple child to AnimatedContainer.
Tried to add text in GestureDetector, but it doesn't work correctly
TopScreen.dart
import 'package:flutter/material.dart';
class TopScreen extends StatefulWidget {
final Widget child; //child widget
TopScreen({this.child});
#override
State createState() => _TopScreenState();
static _TopScreenState of (BuildContext context) {
assert(context != null);
final _TopScreenState result = context.findAncestorStateOfType();
return result;
}
}
class _TopScreenState extends State<TopScreen> {
Color _color = Colors.white;
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedContainer(
child: widget.child,
color: _color,
duration: Duration(milliseconds: 500),
),
);
}
void setColor(Color color) {
this._color = color;
}
}
main.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:solidsoftwaretest/TopScreen.dart';
void main() => runApp(MaterialApp(home: TopScreen(child: MainScreen())));
class MainScreen extends StatefulWidget {
#override
State createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
TopScreen.of(context).setColor(Colors.primaries[Random().nextInt(Colors.primaries.length)]);
TopScreen.of(context).setState(() {});
},
);
} //build
}
UPD: if I add Child (Text) to GestureDetector - gestureDetector works only on Text.
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Center(
child: Text('Hey there!')
),
onTap: () {
TopScreen.of(context).setColor(Colors.primaries[Random().nextInt(Colors.primaries.length)]);
TopScreen.of(context).setState(() {});
}
);
} //build
}
You can add Text widget inside a Container & wrap it further in GestureDetector. In order to make GestureDetector work on the whole area, give the Container a transparent color as follows:
return GestureDetector(
onTap: () {
TopScreen.of(context).setColor(Colors.primaries[Random().nextInt(Colors.primaries.length)]);
TopScreen.of(context).setState(() {});
},
child: Container(
color: Colors.transparent,
alignment: Alignment.center,
child: Text('Hey there'),
),
);
Hope, it will help
You should be able to put a Column inside your AnimatedContainer. The Column will hold multiple Widgets.
I've updated the code to show the full example.
ScaffoldColorCenterText60597839(
child: Text('Bananas'),
)
class ScaffoldColorCenterText60597839 extends StatefulWidget {
final Widget child;
ScaffoldColorCenterText60597839({
this.child
});
#override
_ScaffoldColorCenterText60597839State createState() => _ScaffoldColorCenterText60597839State();
}
class _ScaffoldColorCenterText60597839State extends State<ScaffoldColorCenterText60597839> {
Color _color = Colors.white;
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedContainer(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
widget.child,
Text('A text widget')
],
)
),
color: _color,
duration: Duration(milliseconds: 500),
),
);
}
}

How to lift state from a child widget up to another child widget

How would I lift a data from a child widget up to another child widget that's directly above it.
import 'package:flutter/material.dart';
class Tree extends StatefulWidget {
#override
_TreeState createState() => _TreeState();
}
class _TreeState extends State<Tree> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
bigTree(),
],
),
);
}
}
Widget bigTree() {
var fruit = data; //Undefined name 'data'.
return Container(
child: Row(
children: <Widget>[
Text(fruit),
smallTree(),
],
),
);
}
Widget smallTree() {
var data = 'apple'; //How to lift this data
return Container(
child: Text(data),
);
}
I tried adding a constructor `_TreeState({this.data}); but it's coming up as null
What you have aren't actually widgets. They're functions returning widgets. If you want the bigTree to "hold state" in flutter's traditional meaning, you'd need to make it into a class which extends StatefulWidget and implements create state as your Tree widget does. Currently, as functions, every time you'd hot reload, the variables in your widget returning functions would be reset and thus no "state" is present.
Here is how I'd change things
import 'package:flutter/material.dart';
class Tree extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
BigTree(),
],
),
);
}
}
class BigTree extends StatefulWidget {
#override
_BigTreeState createState() => _BigTreeState();
}
class _BigTreeState extends State<BigTree> {
final fruit = 'apple';
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: <Widget>[
Text(fruit),
SmallTree(fruit),
],
),
);
}
}
class SmallTree extends StatelessWidget {
SmallTree(this.data);
final String data;
#override
Widget build(BuildContext context) {
return Container(
child: Text(data),
);
}
}
I think Nolence answer is good. I want to give more general. I am begginer, however this is how I understand:
With simple setState.
You let parent widget to manage the state. You pass all usefull 'tools' to the stateless child widget where action occurs.
class ChildWidget extends StatelessWidget {
// used tools
final var inputData;
final Function changeData;
ChildWidget(this.inputData, this.changeData);
#override
Widget build(BuildContext context) {
return Container(
...
// action
onTap: () {
changeData(inputData);
},
// data usage
Text('My changed ${inputData.toString()}'),
Implement logic (define tools) in the parent widget:
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
// define tools and pass to child
var inputData = 0;
void changeData(var outputData) {
// Implement logic
print(outputData);
setState(() {
inputData += outputData;
});
}
#override
Widget build(BuildContext context) {
return Container(
...
ChildWidget(inputData, changeData),
When the changeData function will be used it will call statefull widget to change the state and rebuild the tree (render).

statfulWidget with key concept

i am studying key in flutter. and in explanation, when i want swap widget in statefulWidget i need to add key value. because when flutter check element structure if type, state are not same they don't response. this is how i understand.
void main() => runApp(new MaterialApp(home: PositionedTiles()));
class PositionedTiles extends StatefulWidget {
#override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
StatefulColorfulTile(key: UniqueKey()), // Keys added here
StatefulColorfulTile(key: UniqueKey()),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}
swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR
#override
ColorfulTileState createState() => ColorfulTileState();
}
class ColorfulTileState extends State<ColorfulTile> {
Color myColor;
#override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}
#override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}
but i saw this code.
Widget build(BuildContext context) {
return Column(
children: [
value
? const SizedBox()
: const Placeholder(),
GestureDetector(
onTap: () {
setState(() {
value = !value;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
!value
? const SizedBox()
: const Placeholder(),
],
);
}
this code is also use statefulWidget. in this code when user taps Box it's changed but i think there're no key value and in element structure there are different type(one is SizedBox and the other is placeHolder) so i think there aren't changed. why they're changed? what i misunderstand?