How do I update the state of a parent widget onTap of a child widget using provider
Currently what I am doing is pass a function from parent to child that calls the setState() function at the parent's end (Please refer the psuedocode below), but I'm looking for a more robust way of doing this.
class ParentWidget extends StatefulWidget {
#override
_ParentWidgetState createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
_refresh() {
setState(() {});
}
#override
Widget build(BuildContext context) {
return Container(
child: new ChildWidget(
notifyParent: _refresh(),
),
);
}
}
class ChildWidget extends StatelessWidget {
final Function notifyParent;
ChildWidget({#required this.notifyParent});
#override
Widget build(BuildContext context) {
return Container(
child: new FlatButton(
onPressed: notifyParent(), child: new Text("Update Parent")),
);
}
}
This is the simplest way I could represent the problem, the setState() in the parent (in the actual code) compels the parent widget to update its view.
In the actual code, I have the child widget handling the db queries, a click of the child widget changes the values in the db, and these values are to be updated by parent widget. Hence when I click the child widget, it updates the db then calls the notifyParent() which then leads to the parent querying the db again and updating the view.
Is there a better way of doing this without using the setState() by using the provider? How can I use say a ChangeNotifierProvider to notify of the db changes? Or is there any way I can notify the parent widget whenever something in the db is changed so that the parent can refresh itself?
Thank You.
Related
I have a child stateful widget that isn't updating the parent widget when an update to one of the parameters occurs.
The parent widget (OtherListVIew) is used to display a list of items on a PaginatedGrid with each item having a checkbox. The problem is that when a user views the page and there are selected items that are retrieved by the _retrieveItems() the changes do not reflect since at this point the ui is already rendered on the screen with the original empty list. These retrieved values do not reflect on OtherListView (_selectedItems is then used to display the checked items).
In this child widget I have the code
List<String> _selectedItems= [];
#override
void didChangeDependencies() {
super.didChangeDependencies();
WidgetsBinding.instance.addPostFrameCallback((_) {
_retrieveItems(); // this function retrieves items and reinitializes _selectedItems
});
}
#override
Widget build(BuildContext context) {
return SomeListView(
key: _listViewKey,
selectedItems: _selectedItems,
);
}
SomeListView looks as below
class SomeListView extends OtherListView {
const SomeListView ({super.key, super.selectedItems});
#override
State<SomeListView > createState() => SomeListViewState();
}
class SomeListViewState extends OtherListViewState<SomeListViewState> {
// override some method here from OtherListView for customization purposes
#override
Widget build(BuildContext context) {
return super.build(context);
}
}
OtherListView is the parent class that contains the PaginatedGrid that renders the passed _selectedItems to the UI. Why is it that after the _selectedItems have been refetched they are not reflecting on OtherListView? Is this an issue with my state management?
I have attempted using didUpdateWidget under OtherListView and indeed I can see the new values under the newWidget but they are not rendered on the UI. What I'm I missing?
#override
void didUpdateWidget(covariant T oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedItems != oldWidget.selectedItems) {
log.info('OtherListView selected items have been updated.');
}
}
OtherListView extends StatefulWidget {
final List<String>? selectedItems;
const OtherListView({Key? key,this.selectedItems})
: super(key: key);
// ...
}
honestly i don't understand your code but the problem you are facing the parameter is not updating because with setState it will only rebuild the parameter/state/variables that are in the same screen/class widget where the setState is called so you have to raise your parameter to the parent screen then pass it to the child with constructor and create a constructor in your child for function onTap for example then in the parent screen you use that onTap and called setState inside
am playing around with the slider widget on flutter, and I can't figure out why it does not update certain values in a different widget, example code is shown below;
When i move the slider, it has no issues moving, but the value i'm trying to update on the other widget does not update even though the onchanged is updating the variable passed through in a set state accordingly.
any help would be greatly appreciated!
Scaffold Code
class TestPage extends StatelessWidget {
static const id = "test_page";
#override
Widget build(BuildContext context) {
double testValue = 0;
return Scaffold(
body: Column(
children: [
Text("Hello World"),
TestBoxNumber(
numberDisplay: testValue,
),
TestSlider(testValue: testValue),
],
),
);
}
}
Slider Code
class TestSlider extends StatefulWidget {
double testValue;
TestSlider({required this.testValue});
#override
_TestSliderState createState() => _TestSliderState();
}
class _TestSliderState extends State<TestSlider> {
#override
Widget build(BuildContext context) {
return Slider(
activeColor: themeData.primaryColorLight,
value: widget.testValue,
min: 0,
max: 100,
divisions: 100,
label: widget.testValue.round().toString(),
onChanged: (double value) {
setState(() {
widget.testValue = value;
});
},
);
}
}
Different Widget Code
class TestBoxNumber extends StatefulWidget {
final double numberDisplay;
const TestBoxNumber({required this.numberDisplay});
#override
_TestBoxNumberState createState() => _TestBoxNumberState();
}
class _TestBoxNumberState extends State<TestBoxNumber> {
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.numberDisplay.toString()),
);
}
}
The problem is that you are constructing TestBoxNumber widget in such a way that value (testValue) will always be the same (testValue is never returned out of the TestSlider widget).
How to overcome this issue?
You can make your TestPage a StatefullWidget. Then create callback from TestSlider, so when you change value in TestSlider you will call some function in TestPage (with setState in it, causing re-rendering your page).
Or if you don't want your whole TestPage widget to be Statefull (if, let's say, you predict a lot of other static widgets in it and you don't want them to be re-rendered because you just moved a slider), you can create wrapper Statefull widget and put both TestSlider and TestBoxNumber widgets in it. This is more flexible approach, imho.
Here is small scheme of what I mean by wrapping two widgets in another one:
UPD: btw, there is no point in making TestBoxText a statefull widget if it's only purpose is to display a text and you pass it's value through the constructor.
I have seen many times people calling widget. sth inside the code.
May I know what it is actually doing?
For example code below, (highlighted part is my confusion)
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
In flutter's StatefulWidget, we have the following architecture.
You have a StatefulWidget like this,
class MyOwnClass extends StatefulWidget {
State createState () => _MyOwnClassState();
}
And you have a State class for your StatefulWidget like this,
class _MyOwnClassState extends State<MyOwnClass> {
}
Now, State class is meant to house variables that tend to change in order for your UI to be rebuilt.
So you can have variables in your State that you can update using setState.
But what if you had some data that doesn't change and you want to avoid putting them inside the State class.
That's where your StatefulWidget comes to play.
You can store variables in your MyOwnClass and the widget variable inside the State class gives you a way to access them.
For example,
class MyOwnClass extends StatefulWidget {
int numberThatDoesntChange = 1;
State createState () => _MyOwnClassState();
}
You can access them in your State class like this,
class _MyOwnClassState extends State<MyOwnClass> {
Widget build(BuildContext context) {
return Text('$widget.numberThatDoesntChange');
}
}
Apart from this, your StatefulWidget has many more internal instance members that you can access inside of your State class using the widget variable.
The widget refers to the actual view that renders on the screen. It extends the StatefulWidget class of the flutter framework and overrides the createState() method. The createState() method is used to create the instance of state class. We will look into createState().
The state class is used to maintain the state of the widget so that it can be rebuilt again. It extends the State class of the flutter framework and overrides the build method.
The framework calls build() method again and again whenever setState() method is called. The setState() method notifies the framework that the internal state of this object has changed and it should be rebuilt. Suppose we change the value of text in StatefulWidget then we need to call setState().
Edit As Nisanth pointed outh in his comment - I missed your question completely; please ignore the below....
Let me try my answer, I don't think others are getting your point.
In your exapmle, Column(children: x) expect a list of Widgets.
You have two options - either provide this list directly:
class _MyOwnClassState extends State<MyOwnClass> {
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: <Widget>[SomeWidget()]),
),
);
}
}
Or if you have more complex code that generates widget - based on input parameters, or you have the same widget generated multiple times and you want to avoid the code duplication - you would create the separate function to do the job.
Something like:
class _MyOwnClassState extends State<MyOwnClass> {
List<Widget> makeWidgetChildren(int param) {
/*
some very complex logic here
/*
if (param>3 && param<4) {
return List<Widget>.generate(4, (index)=>SomeWidget1(index));
} else {
return <Widget>[Center()];
}
}
#override
Widget build(BuildContext context) {
return ListTile(
title: Container(
child: Column(children: makeWidgetChildren(**widget.jsonObject)**),
),
);
}
}
So basically, it is just to make the code nicer; and to avoid having code repeated over and over again in the build function.
I'm learning flutter and trying to make a kind of MutableImage widget. The idea is to make a MutableImage StatefulWidget that would rebuild when a new image is provided. I try to avoid rebuilding the whole widget tree each time the image is changed because that seems overkill, and I plan to update the image several times per second. So I want to rebuild only that MutableImage widget.
So here is the code I have, with comments to explain where I'm stuck :
class MutableImage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MutableImageState();
}
void updateImage(List<int> bytes) {
// !!!!!! Would like to call this method here, but state is not available from Widget, which means I want to do something wrong, but not sure exactly how I should do it...
// this.state.updateImage(bytes);
}
}
class MutableImageState extends State<MutableImage> {
List<int> _bytes;
void updateImage(List<int> bytes) {
setState(() {
_bytes=bytes;
});
}
#override
Widget build(BuildContext context) {
if ((_bytes==null)||(_bytes.length==0)) {
return Center(child: CircularProgressIndicator());
}
return Image.memory(_bytes);
}
}
Then the idea was to use this widget like this for example in another stateful widget
MutableImage _mutableImage;
#override
Widget build(BuildContext context) {
if (_mutableImage == null) _mutableImage=MutableImage();
return : Row( //using Row as an example, the idea is that the mutable Image is deep into a tree of widgets, and I want to rebuild only _mutableImage when image changes, not all widgets.
children : <Widget>[
child0, child1, _mutableImage, child3, child4
]
);
}
void updateImage(List<int> bytes) {
_mutableImage?.updateImage(bytes);
}
So is there a good way to do this ? I'm quite confused, thx for any help/hint.
This is a place for an application of a GlobalKey. In the parent Widget of MutableImage make a global key and pass that to MutableImage. With that key you can access MutableImage state by using .currentState on the key and calling updateImage.
You'll have to add key as an argument of the MutableImage constructor and call super(key: key). updateImage should also be moved the the state of MutableImage.
Key:
final GlobalKey<MutableImageState> _imageKey = GlobalKey<MutableImageState>();
Pass the key:
MutableImage(key: _imageKey);
Access the state:
_imageKey.currentState.updateImage();
I just got calling methods from a child class working with help from someone in this thread.
What I am trying to do now, and I am not sure if it is different, is call a method in one child from another child of the same parent.
So visually:
Parent class
- Method()
^
|
Child class
In the above, I can easily access the Parent class method from the child class using the callback function in the link provided above.
This does not appear to work in the below structure, and I can't figure this out from any of the threads I have read on calling methods from other classes:
Parent class
| |
Child class 1 Child class 2
- Method() <-- callback
Is the procedure for this structure handled differently? Is it possible or can you only ever callback to a parent method?
Although I think, in flutter, it would be better to use state changes to update/trigger calls on UI widgets, but for your speciffic case, the delegate pattern can work. Here's an example of I would do it.
abstract class TheTrigger { // you can use VoidCallback or whatever, this is just for the demo
void triggerMe();
}
class ChildOneWidget extends StatelessWidget with TheTrigger {
#override
Widget build(BuildContext context) {
return Container(); // add the content of the child one
}
#override
void triggerMe() {
// TODO: implement triggerMe
}
}
class ChildTwoWidget extends StatelessWidget {
final TheTrigger trigger;
const ChildTwoWidget({Key key, this.trigger}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
//something here that will trigger "the trigger"
child: RaisedButton(onPressed: () {
trigger?.triggerMe(); // you should use the "?" this will allow a bit more customisation on your widgets, you might want to use it without listener.
}),
);
}
}
class ParrentWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
ChildOneWidget childOneWidget = ChildOneWidget();
ChildTwoWidget childTwoWidget = ChildTwoWidget(trigger: childOneWidget); // here you set the "delegate"
return ListView(
children: <Widget>[childOneWidget, childTwoWidget],
);
}
}
One more time, this is just a dumb example on how you can do it, but I would strongly recommend to use state to trigger changes on children, you'll have a more flexible widget tree.
The way flutter widgets work is that the children widgets in your case are in the parents widget tree. On the callback of Child class 2 you can use setState to rebuild your parent and thus rebuild any of its children, for example by changing the value of a parameter in Child class 1