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"),
),
);
}
}
Related
I'm quite new to flutter and i'm struggling to get my head around passing variables up the widget tree. I've written a very simple code to demonstrate what i'm trying to achieve and I was hoping someone could please spell it out for me.
I have a parent Stateful widget with a counter in it:
class ParentWidget extends StatefulWidget {
const ParentWidget({Key? key}) : super(key: key);
#override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int Counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(
height: 100,
),
Button(),
],
),
),
);
}
}
then I have another Stateful Widget with the button and bool in it:
class Button extends StatefulWidget {
const Button({Key? key}) : super(key: key);
#override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
bool buttonPressed = false;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
buttonPressed = !buttonPressed;
print(buttonPressed);
});
},
child: Container(
color: kWhite,
height: 50,
width: 50,
),
);
}
}
I've looked at some of the other answers (using callbacks?) but am struggling to understand how it actually works and how I would implement it into my code
How do I pass the bool variable up the tree to change the counter?
thanks so much and any help would be greatly appreciated
Add the callback function in the Button widget that returns the state of the button pressed.
In Parent widget, add the argument for the callback function which returns the button pressed state.
class ParentWidget extends StatefulWidget {
const ParentWidget({Key? key}) : super(key: key);
#override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int Counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
const SizedBox(
height: 100,
),
Button(isPressed: (isPressed) => print(isPressed)), <-- updated
],
),
),
);
}
}
class Button extends StatefulWidget {
final Function(bool) isPressed; <-- updated
const Button({Key? key, required this.isPressed}) : super(key: key); <-- updated
#override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
bool buttonPressed = false;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
buttonPressed = !buttonPressed;
print(buttonPressed);
});
widget.isPressed(buttonPressed); <-- updated
},
child: Container(
color: Colors.white,
height: 50,
width: 50,
),
);
}
}
This is how to pass data to parent widget using a callback,
class ParentWidget extends StatefulWidget {
const ParentWidget({Key? key}) : super(key: key);
#override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
int Counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(
height: 100,
),
Button(
// here you will get the bool
onBtnPressed: (val) {
print(val);
},
),
],
),
),
);
}
}
class Button extends StatefulWidget {
const Button({required this.onBtnPressed, Key? key}) : super(key: key);
// add this here, this function will act as the callback
final Function onBtnPressed;
#override
State<Button> createState() => _ButtonState();
}
class _ButtonState extends State<Button> {
bool buttonPressed = false;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
buttonPressed = !buttonPressed;
print(buttonPressed);
});
widget.onBtnPressed(buttonPressed);
},
child: Container(
color: kWhite,
height: 50,
width: 50,
),
);
}
}
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),
));
}
}
I would like to update a child's state when a button is clicked in the parent, so for example:
class Parent extends StatelessWidget{
Widget build(context){
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () => //somehow increment the child's counter,
icon: const Icon(Icons.add),
),
],
),
body: const Child(),
);
}
}
class Child extends StatefulWidget {
const Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
...
int counter = 0; //to be incremented when parent's button is clicked on.
...
}
Is there a common way to implement this? From the other posts I've read, people usually use the child to update the parent's state via callback, so if there is a way to refactor my code to acheive the same effect, that would help too.
You can create the field counter in the parent and pass it down to the child widget and update the child widget from the parent.
You can check the demo that I made here..
DartPad Demo Link
statemanagement Method
You can use provider,bloc,cubit,getx... package to update the child and parent value
setstate callback (here i mention)
Change you widget like this .your parent widget to stateful.
int counter = 0;
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
SampleCod Dartpad live code check here
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
Widget build(context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
onPressed: () {
setState(() {
counter++;
});
},
icon: const Icon(Icons.add),
),
],
),
body: Child(),
);
}
}
int counter = 0;
class Child extends StatefulWidget {
Child({Key? key}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(child: Text("$counter",style: TextStyle(fontSize: 30),));
} //to be incremented when parent's button is clicked on.
}
Try this:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: Parent(),
);
}
}
class Parent extends StatefulWidget {
const Parent({Key? key}) : super(key: key);
#override
State<Parent> createState() => _ParentState();
}
class _ParentState extends State<Parent> {
int counter = 0;
void incrementCounter() {
setState(() {
counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
tooltip: "Increment counter",
onPressed: incrementCounter,
icon: const Icon(
Icons.add,
),
),
],
),
body: Child(
counter: counter,
),
);
}
}
class Child extends StatefulWidget {
const Child({
Key? key,
required this.counter,
}) : super(key: key);
final int counter;
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) {
return Center(
child: Text(
widget.counter.toString(),
style: const TextStyle(
fontSize: 30,
),
),
);
}
}
I would like when I press on the RawMaterialButton, the textField () class changes to the container () class and appears on the screen,
with the code I have now when I press nothing happens ...
can anyone fix it? Thank you.
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: chat(),
);
}
}
class chat extends StatefulWidget {
const chat({Key? key}) : super(key: key);
#override
_chatState createState() => _chatState();
}
class _chatState extends State<chat> {
bool changeClass = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: changeClass ? container() : textField(changeClass: changeClass),
);
}
}
class textField extends StatefulWidget {
textField({Key? key, required this.changeClass}) : super(key: key);
bool changeClass = false;
#override
_textFieldState createState() => _textFieldState();
}
class _textFieldState extends State<textField> {
#override
Widget build(BuildContext context) {
return Center(
child: Row(
children: [
Container(
width: 300.0,
height: 60.0,
color: Colors.red,
),
RawMaterialButton(
onPressed: () {
setState(() {
widget.changeClass = true;
});
},
child: Icon(Icons.send),
)
],
),
);
}
}
class container extends StatefulWidget {
const container({Key? key}) : super(key: key);
#override
_containerState createState() => _containerState();
}
class _containerState extends State<container> {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
width: double.infinity,
height: 60.0,
color: Colors.grey,
),
);
}
}
hope someone can help me.
Thank you :)
I write this piece because otherwise it won't let me upload itI write this piece because otherwise it won't let me upload itI write this piece because otherwise it won't let me upload it
You are technically changing a local variable in the _textFieldState, so to solve the problem you have multiple options, one of them is to pass a function that change the state in the _chatState, this code would do so:
void main() => runApp(mainApp());
class mainApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: chat(),
);
}
}
class chat extends StatefulWidget {
const chat({Key? key}) : super(key: key);
#override
_chatState createState() => _chatState();
}
class _chatState extends State<chat> {
bool changeClass = false;
changeClassValue() {
setState(() {
changeClass = !changeClass;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: changeClass
? container()
: textField(changeClassValue: changeClassValue),
);
}
}
class textField extends StatefulWidget {
textField({Key? key, required this.changeClassValue}) : super(key: key);
Function changeClassValue;
#override
_textFieldState createState() => _textFieldState();
}
class _textFieldState extends State<textField> {
#override
Widget build(BuildContext context) {
return Center(
child: Row(
children: [
Container(
width: 300.0,
height: 60.0,
color: Colors.red,
),
RawMaterialButton(
onPressed: () {
setState(() {
widget.changeClassValue();
});
},
child: Icon(Icons.send),
)
],
),
);
}
}
class container extends StatefulWidget {
const container({Key? key}) : super(key: key);
#override
_containerState createState() => _containerState();
}
class _containerState extends State<container> {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
width: double.infinity,
height: 60.0,
color: Colors.grey,
),
);
}
}
where the output would look like:
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);
}