Triggering child method from stateful parent widget flutter - flutter

I have two files
Parent.dart and Chid.dart
Parent.dart:
------------
class Parent extends StatefulWidget {
const Parent ({super.key});
#override
State<Parent > createState() => _ParentState();
}
class _ParentState extends State<Parent> {
#override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(Icons.addbox),
onPressed: (){//Call Method from the child},
),
}
}
and
Child.dart:
------------
class Child extends StatefulWidget {
const Child ({super.key});
#override
State<Child> createState() => _ChildState();
}
class _ChildState extends State<Child> {
void myFunciton()
{
//do something cool.
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I can send data from parent to child and call methods from parent to child but what I was looking for is a way to access a child method from the parent. Is there a way to call the method from the child when the button is pressed?

First you need to change _ChildState to ChildState (remove the underlined) for outside dart file can access it
Then in parent.dart, declare a GlobalKey, pass it to Child widget and call function like this:
class _ParentState extends State<Parent> {
final key = GlobalKey<ChildState>();
#override
Widget build(BuildContext context) {
return Column(
children: [
Child(key: key), //I assume you have a child widget here
IconButton(
icon: Icon(Icons.addbox),
onPressed: () { //Call Method from the child},
key.currentState?.myFunciton();
}
),
],
);
}
}

Related

Track widget update from different widget

I need to update a widget whenever the widget that's nested to it is updated.
Let's say widget A is nested in widget B, by the way we can have GlobalKey of widget A (if it can help to detect if the widget A is updated). Here, I need to update widget B whenever the widget A is updated, and in order to do it I need to check if the widget A is updated, if it is then I'll update widget B too.
You could do this for example:
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: B()));
}
class B extends StatefulWidget {
const B({super.key});
#override
State<B> createState() => BState();
}
class BState extends State<B> {
#override
Widget build(BuildContext context) {
print('B updates');
return Scaffold(
body: Column(
children: [A(updater: update)],
));
}
void update() {
setState(() {
});
}
}
class A extends StatefulWidget {
final Function? updater;
const A({Key? key, this.updater}) : super(key: key);
#override
State<A> createState() => _AState();
}
class _AState extends State<A> {
#override
void setState(VoidCallback fn) {
if (widget.updater != null) widget.updater!();
super.setState(fn);
}
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
setState(() {
print('clicked button');
});
},
child: const Text('click'));
}
}
Whenever the button is clicked it prints both "clicked button" as "B updates". So B rebuilds on each click. If you leave out the setState override in A you will see only the "clicked button" prints.

Child Property in Stateful Widget

I've made some test widgets to illustrate a point that I'm having difficulty with in a much more complicated widget.
I have the following widget:
class TestListWidget extends StatefulWidget {
Widget child;
TestListWidget({Widget child}) {
this.child = child;
}
#override
State<StatefulWidget> createState() {
return TestListWidgetState();
}
}
class TestListWidgetState extends State<TestListWidget>
{
Widget child;
int buttonCount = 0;
#override initState() {
child = widget.child;
}
_clickedCountButton()
{
setState(() {
buttonCount++;
});
}
#override
Widget build(BuildContext context) {
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
child
]);
}
}
The above widget is being used inside the following widget:
class TestList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new TestListState();
}
}
class TestListState extends State<TestList> {
String _testStr = "not clicked";
_clickButton()
{
setState(() {
_testStr = "CLICKED";
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
primarySwatch: Colors.blue
),
home:Scaffold(
body : testListWidget(child: Row(children: [
Text(_testStr),
ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
]))));
}
}
The issue I'm having is when the "Click me!" button is clicked, the function is called, but the text on the screen is not updated to "CLICKED!". The "Update Count" button works as intended though.
If I make the TestListWidget a stateless widget (and remove the update count button functionality) then the "Click Me!" button works as expected. Is there any way to make a child widget rebuild when passed to a stateful widget?
Your problem is quite simple. You have two variables, one is TestListWidget.child, we'll call it stateful widget's child. The second is TestListWidgetState.child, we'll call it state's child.
You make state's child to be equal to stateful widget's child on initState, but initState only runs when you first create a state, so updating the stateful widget's child will not update the state's child because initState won't run again.
To fix this, I believe you can just completely remove state's child, and use widget.child instead:
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
widget.child
]);
Full example:
import 'package:flutter/material.dart';
void main() => runApp(TestList());
class TestListWidget extends StatefulWidget {
Widget child;
TestListWidget({required this.child});
#override
State<StatefulWidget> createState() {
return TestListWidgetState();
}
}
class TestListWidgetState extends State<TestListWidget>
{
int buttonCount = 0;
_clickedCountButton()
{
setState(() {
buttonCount++;
});
}
#override
Widget build(BuildContext context) {
return new Column(children: [
Text("Times Hit: $buttonCount"),
ElevatedButton(onPressed: _clickedCountButton, child: Text("Update Count")),
widget.child
]);
}
}
class TestList extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new TestListState();
}
}
class TestListState extends State<TestList> {
String _testStr = "not clicked";
_clickButton()
{
setState(() {
_testStr = "CLICKED";
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
theme: ThemeData(
primarySwatch: Colors.blue
),
home:Scaffold(
body : TestListWidget(child: Row(children: [
Text(_testStr),
ElevatedButton(onPressed: _clickButton, child: Text("Click me!"))
]))));
}
}

How can I call setState of a child widget from a parent widget?

Whenever the user clicks on the back button of the device, I need to change a variable value inside the child widget and call setState(). The thing is that when I click the back button, the onWillPopScope() of the parent widget is called and not of the child widget. So, I need from the parent to change the variable value of the child widget state and call setstate() inside the child widget. Or if there is a way to do that the onWillPopScope of the child widget will be called, it would also solve my problem. Is there anything I can do?
You just need to add a field to child widget then in parent widget change value of that field and call setState().
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
_ParentState createState() => _ParentState();
}
class _ParentState extends State<Parent> {
String _text;
#override
Widget build(BuildContext context) => Column(
children: <Widget>[
Child(param: _text),
RaisedButton(
child: const Text('CHANGE VALUE'),
onPressed: () => _updateChild('NEW VALUE'),
)
],
);
void _updateChild(String value) {
_text = value;
setState(() {});
}
}
class Child extends StatefulWidget {
final String param;
const Child({this.param});
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
#override
Widget build(BuildContext context) => Text(widget.param);
}

Flutter UI doesn't update when custom widget is used

I have a Flutter where I display a list of elements in a Column, where the each item in the list is a custom widget. When I update the list, my UI doesn't refresh.
Working sample:
class Test extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return TestState();
}
}
class TestState extends State<Test> {
List<String> list = ["one", "two"];
final refreshKey = new GlobalKey<RefreshIndicatorState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(40),
child: Row(
children: <Widget>[
Container(
child: FlatButton(
child: Text("Update"),
onPressed: () {
print("Updating list");
setState(() {
list = ["three", "four"];
});
},
)
),
Column(
children: list.map((s) => ItemView(s)).toList(),
)
],
),
)
);
}
}
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState(s);
}
class ItemViewState extends State<ItemView> {
String s;
ItemViewState(this.s);
#override
Widget build(BuildContext context) {
return Text(s);
}
}
When I press the "Update" button, my list is updated but the UI is not. I believe this has something to do with using a custom widget (which is also stateful) because when I replace ItemView(s) with the similar Text(s), the UI updates.
I understand that Flutter keeps a track of my stateful widgets and what data is being used, but I'm clearly missing something.
How do I get the UI to update and still use my custom widget?
You should never pass parameters to your State.
Instead, use the widget property.
class ItemView extends StatefulWidget {
String s;
ItemView(this.s);
#override
State<StatefulWidget> createState() => ItemViewState();
}
class ItemViewState extends State<ItemView> {
#override
Widget build(BuildContext context) {
return Text(widget.s);
}
}

Notify parent widget from sub children widgets

I am trying to refresh the parent widget from sub children widget. Actually, there are a number of widgets in between like A uses B and B uses C. I would like to refresh A widget on an event in C widget.I researched a lot but couldn't find an exact answer. A code snipped will be really helpful. Thanks in advance
There are a few solutions:
A pass a callback that does a setState to B, which then pass it to C:
class A extends StatefulWidget {
#override
_AState createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return B(
onSomething: () => setState(() {}),
);
}
}
class B extends StatelessWidget {
final VoidCallback onSomething;
const B({Key key, this.onSomething}) : super(key: key);
#override
Widget build(BuildContext context) {
return C(onSomething: onSomething);
}
}
class C extends StatelessWidget {
final VoidCallback onSomething;
const C({Key key, this.onSomething}) : super(key: key);
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: onSomething,
);
}
}
use NotificationListener in A, and dispatch a Notification from C:
class MyNotification extends Notification {}
class A extends StatefulWidget {
#override
_AState createState() => _AState();
}
class _AState extends State<A> {
#override
Widget build(BuildContext context) {
return NotificationListener<MyNotification>(
onNotification: (_) {
setState(() {});
},
child: B(),
);
}
}
class C extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
MyNotification().dispatch(context);
},
);
}
}
Why do you want to refresh? You updated any data and wanted the new ones to be displayed?
You could try to use Provider widget. With it you can modify any data and notify everyone interested in that data that it changed.
In your setup you could put the provider on the A widget, on the C widget you could get the value, updated and notify everyone. When doing that, A widget will automatically rebuild with the updated information.
The code would be something like this:
class AppState with ChangeNotifier {
AppState();
YourData _data;
void setData(YourData data) {
_data = data;
notifyListeners();
}
}
ChangeNotifierProvider.value(
value: AppState(),
child: WidgetA()
)
WidgetC() {
Provider.of<AppState>(context).setData(yourChangedDataHere);
}