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

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).

Related

Flutter: Why the hashCodes of the following container widgets are changing eveytime I hot-reload the app (save the file)?

If I run the following application and observe the hashCodes for BuildSizedBoxWidget which I create two instances of, I notice that they are the same even when I hot reload the app. Does this mean that they are the same widget but referenced multiple times? ... But in case of BuildContainerWidget the hashCodes change every time I hot reload the app. Why does this happen?
'''
import 'package:flutter/material.dart';
void main() {
runApp(const MyApps());
}
class MyApps extends StatelessWidget {
const MyApps({super.key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Test',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
final List<Widget> widgets = const [
BuildSizedBoxWidget(),
BuildSizedBoxWidget(),
BuildContainerWidget(),
BuildContainerWidget()
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('What is happening'),
),
body: Column(
children: widgets,
),
);
}
}
class BuildSizedBoxWidget extends StatelessWidget {
const BuildSizedBoxWidget({super.key});
#override
Widget build(BuildContext context) {
const Widget widget = SizedBox(height: 50, child: Text('test'));
print(widget.hashCode);
return widget;
}
}
class BuildContainerWidget extends StatelessWidget {
const BuildContainerWidget({super.key});
#override
Widget build(BuildContext context) {
Widget widget = Container(height: 50, color: Colors.red);
print(widget.hashCode);
return widget;
}
}
'''
The variables defined in the body of the build method will be re-initialized during SetState.
Variables specified by the const keyword are not initialized.
There are only three parameters required by SizeBox Widget, and all of them can be initialized.
But Container Widget contains many parameters that cannot be initialized. So Container cannot be specified with the const keyword
If you put them outside the body of the build method, the HasCode will not change
class BuildContainerWidget extends StatelessWidget {
BuildContainerWidget({super.key});
Widget widget = Container(key: Key('value'), height: 50, child: Text('test'));
#override
Widget build(BuildContext context) {
print(widget.hashCode);
return widget;
}
}

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

How to call method in BaseViewModel class from parent class just outside the Widget build(BuildContext context) method

I am using MVVM architecture and used stacked dependency for this. I want to call a method exist in ViewModel class from View class.
In this view class trigger method is Widget build(BuildContext context) So I am unable to get reference of ViewModel class.
Is there any way to achieve this.
For more details I have added my code for Stateless Widget:
class ECRView extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
return ViewModelBuilder<ECRViewModel>.reactive(
onModelReady: (model) {
model.init(context);
},
builder: (context, model, child) => Container(
padding: EdgeInsets.all(AppSize.extraSmall),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: screenSize.width,
height: 1.5,
color: Colors.black12,
),
SizedBox(
height: screenSize.height * .02,
),
],
),
),
viewModelBuilder: () => ECRViewModel(),
);
}
//Trigger ECR Model Method
getTriggered(){
//From here I want to call
}
}
This should work on your case, if you want to use the model on widgets
class _MyWidget extends ViewModelWidget<NameViewModel> {
#override
Widget build(BuildContext context, NameViewModelmodel model) {
//can call model
return //some code
}
}
This is not the best option for stacked architecture
class _MyWidget extends StatefulWidget {
final NameViewModelmodel model;
const _MyWidget({Key key, this.model}) : super(key: key);
#override
__MyWidgetState createState() => __MyWidgetState();
}
class __MyWidgetState extends State<_MyWidget> {
#override
Widget build(BuildContext context) {
return Container();
}
}

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.