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

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

Related

How to setState widget by other widget Flutter ,simplecode below

right widget has gesterdetector that adds a String ("ZzZ") to List;
left widget shows all String there in String list by List view Buildder,
right widget adds "ZzZ" to list after pressing the button successfully but it dosent sets ui state...
in android studio after hot reload it shows all added "ZzZ"
import 'package:flutter/material.dart';
List<String> ListOfZzZ=[];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(children: [
Expanded(child:RightSidewidget()),
Expanded(child:LeftSidewidget())
],
)),
);
}
}
class RightSidewidget extends StatefulWidget {
#override
_RightSidewidgetState createState() => _RightSidewidgetState();
}
class _RightSidewidgetState extends State<RightSidewidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Container(child:Text("add new ZzZ"),),
**onTap: (){
setState(() {
ListOfZzZ.add("ZzZ");
});},);**
}
}
class LeftSidewidget extends StatefulWidget {
#override
_LeftSidewidgetState createState() => _LeftSidewidgetState();
}
class _LeftSidewidgetState extends State<LeftSidewidget> {
#override
Widget build(BuildContext context) {
return Container(child:
ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context,index)=>Text(ListOfZzZ[index])),);
}
}
check the Provider package it can help you achieve what you want, ere is a really good tutorial by the flutter devs showing how to use manage the state of your app and notify widgets of the changes other widgets have.
setState rebuild in very specyfic way. you can read about this in here:
https://api.flutter.dev/flutter/widgets/State/setState.html
in simple world setState call the nearest build (I think this is not full true, but this intuitions works for me)
In your code when you tap right widget and call setState only rightwidget will be rebuild.
So this is the easy solutions:
Make left and right widget statless.
In homescreen in row add gestureDetector(or textButton like in my example) and here call setState. When you do that, all homeSreen will be rebuild so left and right widget too. and your list will be actual. Here is example:
List<String> ListOfZzZ = [];
class homescreen extends StatefulWidget {
#override
_homescreenState createState() => _homescreenState();
}
class _homescreenState extends State<homescreen> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: Row(
children: [
Expanded(
child: TextButton(
onPressed: () => setState(() {
ListOfZzZ.add("ZzZ");
}),
child: RightSidewidget())),
Expanded(child: LeftSideWidget())
],
)),
);
}
}
class RightSidewidget extends StatelessWidget {
const RightSidewidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.amber[50],
child: Text("add new ZzZ"),
);
}
}
class LeftSideWidget extends StatelessWidget {
const LeftSideWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: ListView.builder(
itemCount: ListOfZzZ.length,
itemBuilder: (context, index) => Text(ListOfZzZ[index])),
);
}
}
The hard way, but more elegant and better is to use some state manager like bloc. Here is official site: https://bloclibrary.dev/#/gettingstarted
there is a lot of tutorials and explanations. But this is not solutions for 5 minutes.
Edit: I make some solution with BLoC. I hope this help. I use flutter_bloc and equatable packages in version 7.0.1
void main() {
EquatableConfig.stringify = kDebugMode;
Bloc.observer = SimpleBlocObserver();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('myList'),
),
body: BlocProvider(
create: (context) => MylistBloc()..add(AddToList('Start')),
child: Row(
children: [
Expanded(flex: 1, child: buttonsPanel()),
Expanded(flex: 1, child: ListOfZzZ()),
],
),
),
),
);
}
}
class ListOfZzZ extends StatefulWidget {
const ListOfZzZ({Key? key}) : super(key: key);
#override
_ListOfZzZState createState() => _ListOfZzZState();
}
class _ListOfZzZState extends State<ListOfZzZ> {
late MylistBloc _mylistBloc;
#override
Widget build(BuildContext context) {
return BlocBuilder<MylistBloc, MylistState>(
//builder: (context, state) {return ListView.builder(itemBuilder: (BuildContext context,int index){return ListTile(title: state.positions[index];)},);},
builder: (context, state) {
if (state.positions.isEmpty) {
return const Center(child: Text('no posts'));
} else {
return ListView.builder(
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text(state.positions[index]));
},
itemCount: state.positions.length,
);
}
},
);
}
}
class buttonsPanel extends StatefulWidget {
const buttonsPanel({Key? key}) : super(key: key);
#override
_buttonsPanelState createState() => _buttonsPanelState();
}
class _buttonsPanelState extends State<buttonsPanel> {
late MylistBloc _mylistBloc;
#override
void initState() {
super.initState();
_mylistBloc = context.read<MylistBloc>();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Spam'))},
child: Text('Spam')),
TextButton(
onPressed: () => {_mylistBloc.add(AddToList('Ham'))},
child: Text('Ham')),
],
);
}
class SimpleBlocObserver extends BlocObserver {
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print(error);
super.onError(bloc, error, stackTrace);
}
}
class MylistState extends Equatable {
final List<String> positions;
final int lenght;
const MylistState({this.positions = const <String>[], this.lenght = 0});
#override
List<Object> get props => [positions];
#override
String toString() => 'Lenght: {$lenght} Positions: {$positions}';
#override
MylistState copyWith(List<String>? positions) {
return MylistState(positions: positions ?? this.positions);
}
}
abstract class MylistEvent extends Equatable {
const MylistEvent();
#override
List<Object> get props => [];
}
class AddToList extends MylistEvent {
final String posToAdd;
#override
AddToList(this.posToAdd);
}
class MylistBloc extends Bloc<MylistEvent, MylistState> {
MylistBloc() : super(MylistState(positions: const <String>[]));
#override
Stream<MylistState> mapEventToState(
MylistEvent event,
) async* {
if (event is AddToList) {
yield await _mapListToState(state, event.posToAdd);
}
}
Future<MylistState> _mapListToState(
MylistState state, String posToAdd) async {
List<String> positions = [];
positions.addAll(state.positions);
positions.add(posToAdd);
return MylistState(positions: positions, lenght: positions.length);
}
}
}

Invoking setState on child Stateful from parent Stateful widget

When I am invoking setState in child stateful widget. it is showing an error or warning. Is there any way to call Stateful widget inside Stateful widget without causing an error or any good way to do the same?
Here is my sample code :
parent.dart
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
var title = "Parent";
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
Text(title),
Child(init:true), // <-- Calling Child Widget
],
),
),
);
}
}
child.dart
class Child extends StatefulWidget {
final bool init; // <- Showing warning on removing 'final'
// This class (or a class that this class inherits from) is marked as '#immutable', but one or more of its instance fields aren't final: Child.init
Child({
Key? key,
required this.init,
}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Container(
color: widget.init ? Colors.red : Colors.blue,
child: TextButton(
onPressed: () {
setState(
() {
// widget.init = false;
// want to change 'wiget.init' but its final
// removing final causing warning
},
);
},
child: Text("Click me"),
),
);
}
}
You can pass function that changes init variable.
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
var title = "Parent";
var init = true;
void setInitFalse(){
setState((){
init = false;
})
}
bool getInit(){
return init;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: ListView(
children: <Widget>[
Text(title),
Child(init: getInit(), setInitFalse: setInitFalse()),
],
),
),
);
}
}
And then in child
class Child extends StatefulWidget {
final Function init; // <- Showing warning on removing 'final'
// This class (or a class that this class inherits from) is marked as '#immutable', but one or more of its instance fields aren't final: Child.init
final Function setInitFalse;
Child({
Key? key,
required this.init,
required this.setInitFalse
}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Container(
color: widget.init() ? Colors.red : Colors.blue,
child: TextButton(
onPressed: () {
widget.setInitFalse();
},
child: Text("Click me"),
),
);
}
}

Call a setState of a statefull widget from the stateless widget

I have a stateless widget class that has a widget whose movements need to be tracked. I cannot keep this widget inside the stateful widgets as I don't want the state of this widget to be refreshed.
I have the following code.
import 'package:flutter/material.dart';
import 'package:control_pad/control_pad.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Column(
children: <Widget>[
Expanded(
child: JoystickView(
onDirectionChanged: (degree, direction) {
//Change the state here.
},
),
),
Expanded(
child: MyStateFull(),
),
],
),
);
}
}
class MyStateFull extends StatefulWidget {
#override
_MyStateFullState createState() => _MyStateFullState();
}
class _MyStateFullState extends State<MyStateFull> {
double degree = 10;
double direction = 10;
//Call this from the stateless Widget
void changedDirection(degree, direction) {
setState(() {
this.degree = degree;
this.direction = direction;
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(
"The degree Moved is $degree and the direction is $direction",
style: TextStyle(fontSize: 25, color: Colors.black),
),
);
}
}
This code produces the following output.
I want the direction and degree values to be changed as the joystick is moved.
Thank You.
I tried it myself and found the solution. This can be done using streams. I will post the code just in case someone needs it in the future.
import 'package:flutter/material.dart';
import 'package:control_pad/control_pad.dart';
class MyStateLess extends StatelessWidget {
StreamController<List<double>> _controller = StreamController<List<double>>();
GlobalKey<_MyStateFullState> statefulKey = new GlobalKey<_MyStateFullState>();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
JoystickView(
onDirectionChanged: (degree, direction) {
List<double> temp = new List<double>();
temp.add(degree);
temp.add(direction);
_controller.add(temp);
},
),
MyStateFull(stream: _controller.stream, key: statefulKey),
],
);
}
}
class MyStateFull extends StatefulWidget {
final Stream<List<double>> stream;
MyStateFull({Key key, #required this.stream}) : super(key: key);
#override
_MyStateFullState createState() => _MyStateFullState();
}
class _MyStateFullState extends State<MyStateFull> {
double _degree = 0.0;
double _direction = 0.0;
#override
void initState() {
super.initState();
widget.stream.listen((event) {
setState(() {
_degree = event[0];
_direction = event[1];
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: Text("$_degree, $_direction"),
);
}
}

Flutter - Update parant widget class UI on child button click

I have such kind of scenario
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
),
body: Container(
child: ChildWidget(
listControl: this.sentToScreenBuildJson,
notifyParent: refresh,
),
),
);
}
this is my parent build method where I have added ChildWidget a another statfulscreen and passing is a json and a refresh funtion
as per json child will able to draw UI
and on button click I am able to get callback to refresh method.
refresh() {
print("I get refreshed from child");
setState(() {
print("I get refreshed from child in setState");
this.sentToScreenBuildJson = this.newJson;
});
}
on button click both print get execute but UI is not updating as per newJson.
Like I am expecting that as setState run parent has to call build with passing updated json.
which is not working.
thanks for any help.
When you want to pass data from Child to Parent you should use NotificationListener at parent and dispatch Notification from child.
Instance of Notification class will be having data that you can consume in Parent using NotificationListener.
Mostly all the Flutter Widgets are using this technique, for example tab controller receive OverscrollNotification when user reaches to the last tab and still try to swipe.
Following is the demo that you can use to understand how you can use NotificationListener in your code.
import 'package:flutter/material.dart';
void main() => runApp(ParentWidget());
class ParentWidget extends StatefulWidget {
ParentWidget({Key key}) : super(key: key);
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
String _text = 'You have not pressed the button yet';
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NotificationListener<IntegerNotification>(
onNotification: (IntegerNotification notification) {
setState(() {
print(notification);
_text = 'You have pressed button ${notification.value} times';
});
return true;
},
child: Column(
children: <Widget>[
Text(_text),
ChildWidget(),
],
)
),
),
);
}
}
class ChildWidget extends StatefulWidget {
const ChildWidget({Key key}) : super(key: key);
#override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return RaisedButton(onPressed: (){
IntegerNotification(++_counter).dispatch(context);
},child: Text('Increment counter'),);
}
}
#immutable
class IntegerNotification extends Notification{
final int value;
const IntegerNotification(this.value);
String toString(){
return value.toString();
}
}
Update parant widget class UI on child button click
This is a common use case in flutter and flutter has built in InheritedWidget class for these kind of purpose. You may either directly use it for your purpose or use some ready made package solution which uses InheritedWidget behind the scenes like Provider.
An alternative to #Darish's answer, you can declare a static variable in your class 1, access that static variable in class 2 and then update the state of the variable in the class 2.
For example:
import 'package:flutter/material.dart';
class Demo extends StatefulWidget {
static UserObject userObject;
#override
_Demo createState() => _Demo();
}
class _Demo extends State<Demo> {
#override
void initState() {
Demo.userObject = new UserObject(name: "EXAMPLE NAME");
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xfff3f3f3),
appBar: AppBar(title: Text("DEMO")),
body: InkWell(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => HeroClass()));
},
child: Center(
child: Hero(
tag: "tag-demo-id",
child: Container(
color: Colors.black,
padding: EdgeInsets.all(20),
child: Text("${Demo.userObject.name} -> CLICK HERE",
style: TextStyle(color: Colors.white)))))));
}
}
class HeroClass extends StatefulWidget {
#override
_HeroClassState createState() => _HeroClassState();
}
class _HeroClassState extends State<HeroClass> {
final myController = TextEditingController();
#override
void initState() {
myController.text = Demo.userObject.name;
super.initState();
}
#override
void dispose() {
// Clean up the controller when the widget is removed from the widget tree.
// This also removes the _printLatestValue listener.
myController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("DEMO HERO")),
body: Hero(
tag: "tag-demo-id",
child: Container(
child: TextField(
controller: myController,
),
)),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
Demo.userObject.name = myController.text;
});
},
child: Icon(Icons.save),
));
}
}
// object class
class UserObject {
String name;
UserObject({this.name});
UserObject.fromJson(Map<String, dynamic> json) {
name = json['name'];
}
}

change variables with setState in 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.

Categories