I'm trying to set the text from child widget to parent widget. But the text is not reflecting in parent widget.
Tried to use setState() also but still unable to get expected result.
Following is my code:
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc)
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
TestApp2(this.abc);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
setState(() {
widget.abc = "RANDON TEXT";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}
Am i missing something ?
In your example, a few assumptions were made. I will try to remove one by one.
You pass abc from parent to child and you mutated the child value on press on button. As primitive types are pass by value in dart, change in the value of abc in child will not change the value of parent abc. Refer the below snippet.
void main() {
String abc = "oldValue";
changeIt(abc);
print(abc); // oldValue
}
void changeIt(String abc) {
abc = "newValue";
print(abc); //newValue
}
Let's assume the first one is wrong(for understanding purpose). Then changing the value of abc in child will change the value of abc in parent. But without calling that inside setState of parent, parent will not reflect the change. In your case if you change the code as below, it will change the button text alone on click (as setState of child is called).
new FlatButton(
onPressed: () {
setState(
() {
widget.abc = "RANDON TEXT";
},
);
},
child:
new Text(widget.abc), // setting the text based on abc
color: Colors.red,
),
Instead of using globalState which will be very difficult to maintain/debug as app grows, I would recommend using callbacks. Please refer the below code.
void main() => runApp(new TestApp());
class TestApp extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<TestApp> {
String abc = "bb";
callback(newAbc) {
setState(() {
abc = newAbc;
});
}
#override
Widget build(BuildContext context) {
var column = new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2(abc, callback)
],
);
return new MaterialApp(
home: new Scaffold(
body: new Padding(padding: EdgeInsets.all(30.0), child: column),
),
);
}
}
class TestApp2 extends StatefulWidget {
String abc;
Function(String) callback;
TestApp2(this.abc, this.callback);
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2> {
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: () {
widget.callback("RANDON TEXT"); //call to parent
},
child: new Text(widget.abc),
color: Colors.red,
),
);
}
}
To write the very precise answer. Just use the call back like the above answer use this.
So you want to call the state of ParentScreen from the another function/widget/class. Just follow this code
import 'package:showErrorMessage.dart';
class ParentScreen extends StatefulWidget {
ParentScreen({Key key}) : super(key: key);
#override
_ParentScreenState createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
callback() {
setState(() {});
}
#override
Widget build(BuildContext context) {
String message = "hello";
return Container(
child: showErrorMessage(message, callback);,
);
}
}
And here is the child widget/function/class
import 'package:flutter/material.dart';
showErrorMessage(message, Function callback) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
message,
style: TextStyle(color: Colors.white, fontSize: 16),
),
GestureDetector(
onTap: () {
callback(); // ------ this will change/rebuild the state of its parent class
},
child: Icon(
Icons.refresh,
size: 30,
color: Colors.white,
)),
],
));
}
The point that you are missing is your setState method call. You call the setState of the TestState2.
For fixing that, there are two ways.
First way is to create a GlobalKey(https://docs.flutter.io/flutter/widgets/GlobalKey-class.html) and pass it as a parameter to the child widget.
And the second way is to create a global variable for the parent state and use it in the child state.
I modified the code below with the second approach.
_TestState _globalState = new _TestState();
class TestApp extends StatefulWidget {
#override
_TestState createState() => _globalState;
}
class _TestState extends State<TestApp>{
String abc = "";
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Column(
children: <Widget>[
new Text("This is text $abc"),
TestApp2()
],
),
),
);
}
}
class TestApp2 extends StatefulWidget {
TestApp2();
#override
_TestState2 createState() => new _TestState2();
}
class _TestState2 extends State<TestApp2>{
#override
Widget build(BuildContext context) {
return new Container(
width: 150.0,
height: 30.0,
margin: EdgeInsets.only(top: 50.0),
child: new FlatButton(
onPressed: (){
_globalState.setState((){
_globalState.abc = "Button clicked";
});
},
child: new Text("BUTTON"),
color: Colors.red,
),
);
}
}
Related
Let's say we have class Car (Stateless) that have inside it two class for now it will be Wheel (Statefull) and Mask (Statefull) and my job is to whenever state of class Wheel is changed call class Mask to change it state also with specific data from Wheel, but parent should have also access to child data. How can I achieve it?
import 'package:flutter/material.dart';
void main() {
runApp(Car());
}
class Car extends StatelessWidget {
int childMaskVal = ..??????
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('App bar'),
),
body: Column(
children: <Widget>[
Wheel(),
Mask(),
],
),
),
);
}
}
class Wheel extends StatefulWidget {
_WheelState createState() => _WheelState();
}
class _WheelState extends State<Wheel> {
int _value = 0;
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
_value++;
});
},
),
Text(_value.toString()),
],
),
);
}
}
class Mask extends StatefulWidget {
_MaskState createState() => _MaskState();
}
class _MaskState extends State<Mask> {
int _value = 13;
#override
Widget build(BuildContext context) {
return Container(
child: Text((_value * Wheel._value).toString()),???????
);
}
}
You can pass the callback to the child.
In the example you provided, the parent should be a statefulWidget and the children should be a stateless widget. You should initialize the state in the parent and then pass it to the child.
If you find that the parent needs to access the data in the child, that means you need to move the state up.
See the example below:
import 'package:flutter/material.dart';
void main() {
runApp(Car());
}
class Car extends StatefulWidget{
#override
_CarState createState() => _CarState();
}
class _CarState extends State<Car> {
int childMaskVal = ..??????
int _value = 1;
#override
Widget build(BuildContext context) {
return Container(
child: Scaffold(
appBar: AppBar(
title: Text('App bar'),
),
body: Column(
children: <Widget>[
Wheel(
value: _value,
onPressed: () {
setState(() {
_value++;
// update state here
})
}),
Mask(),
],
),
),
);
}
}
class Wheel extends Stateless {
int value;
Function onPressed;
Wheel({this.value, this.onPresed})
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: onPressed
),
Text(value.toString()),
],
),
);
}
}
You can also check this post How to pass data from child widget to its parent
I have a scene (collections.dart) that takes an index of several other scenes/files in a PageView.builder. You can swipe between scenes from the collections.dart file. Also in collections.dart is a button.
I want it to be the case that if you click on the button, and the current scene being shown through collections.dart is, for example, FirstScreen, then I can route to a table I have built specifically for first.dart, with the same being true for all other scenes in the index.
I have tried to accomplish this by a conditional statement in the onPressed argument, but no success yet. There is no error, it just takes no action. Here is the code in its entirety for collections.dart (including the unsuccessful conditional statement for onPressed):
import 'package:flutter/material.dart';
import 'package:circle_indicator/circle_indicator.dart';
import 'first.dart';
import 'second.dart';
import 'third.dart';
import 'fourth.dart';
import 'fifth.dart';
import 'sixth.dart';
import 'seventh.dart';
import 'eighth.dart';
import 'ninth.dart';
import 'tenth.dart';
class CollectionsScreen extends StatelessWidget {
#override
Widget build(BuildContext context){
return Collections();
}
}
class Collections extends StatefulWidget {
#override
CollectionsState createState() => CollectionsState();
}
class CollectionsState extends State<Collections> {
FirstScreen one;
SecondScreen two;
ThirdScreen three;
FourthScreen four;
FifthScreen five;
SixthScreen six;
SeventhScreen seven;
EighthScreen eight;
NinthScreen nine;
TenthScreen ten;
List<Widget> pages;
#override
void initState() {
one = FirstScreen();
two = SecondScreen();
three = ThirdScreen();
four = FourthScreen();
five = FifthScreen();
six = SixthScreen();
seven = SeventhScreen();
eight = EighthScreen();
nine = NinthScreen();
ten = TenthScreen();
pages = [one, two, three, four, five, six, seven, eight, nine, ten];
super.initState();
}
final PageController controller = new PageController();
#override
Widget build(BuildContext context){
return new Stack(
children: <Widget>[
new Scaffold(
body: new Container(
child: new PageView.builder( //Swipe Between Pages
controller: controller,
itemCount: 10,
itemBuilder: (context, index){
return pages[index];
}
),
),
),
new Container( //CircleIndicator
child: new CircleIndicator(controller, 10, 8.0, Colors.white70, Colors.white,),
alignment: Alignment(0.0, 0.9),
),
new Container( //Button
alignment: Alignment(0.0, 0.65),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Container(
child: new RaisedButton(
elevation: 4.0,
child: new Text(
'SHOW ME',
style: new TextStyle(
fontWeight: FontWeight.w900,
fontSize: 22.0,
),
),
color: Color(0xFF70E0EF),
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(7.5)
),
//This is the conditional statement I'm talking about
onPressed: () {
new PageView.builder(
controller: controller,
itemBuilder: (context, index) {
if (pages[index] == one){
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new FirstTable()),
);
}
else if (pages[index] == two){
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new SecondTable()),
);
}
else {
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new ThirdTable()),
);
}
}
);
},
),
width: 150.0,
height: 60.0,
),
],
),
),
],
);
}
}
The "Table" classes I'm referring to in the conditional statement are in the files for first.dart, second.dart, etc. Here is the file for first.dart. For the moment, the code is identical between all these files (first.dart, second.dart, etc.):
import 'package:flutter/material.dart';
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new First();
}
}
class First extends StatefulWidget {
#override
FirstState createState() => FirstState();
}
class FirstState extends State<First>{
#override
Widget build(BuildContext context) {
double fontSize = MediaQuery.of(context).size.height;
double fontSizeFractional = fontSize * 0.07;
return Scaffold(
body: new Stack(
fit: StackFit.passthrough,
children: [
new Container( //Background
decoration: new BoxDecoration(
image: new DecorationImage(
image: new AssetImage('assets/FirstBG.png'),
fit: BoxFit.cover
),
),
),
new Container( //Title
margin: EdgeInsets.all(40.0),
alignment: new Alignment(0.0, -0.70),
child: new Text(
'FIRST',
style: new TextStyle(
fontWeight: FontWeight.bold,
fontSize: fontSizeFractional,
color: Colors.white,
fontFamily: 'baron neue',
),
),
),
],
),
);
}
}
class FirstTable extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Table();
}
}
class Table extends StatefulWidget {
#override
TableState createState() => TableState();
}
class TableState extends State<Table>{
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: new RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text(
'Go Back',
),
),
),
);
}
}
I have a theory that the reason it isn't working is that collections.dart isn't actually caching any data as to what page of the index it is on (that could be totally wrong, though). Curious to hear your ideas!
Your problem is that you should directly use controller.page inside the onPressed of your button. instead of instantiating a widget.
Although ultimately you should hide an abstract layer between your gallery class and the list of items.
To do that you can create a custom class which will hols all informations about a gallery item :
#immutable
class GalleryItem {
final Widget content;
final Widget details;
GalleryItem({#required this.content, this.details}) : assert(content != null);
}
Your gallery will then take a list of such class as parameter. And do it's job with these.
Ideally you want to use your gallery like this :
Gallery(
items: [
GalleryItem(
content: Container(
color: Colors.red,
),
details: Text("red"),
),
GalleryItem(
content: Container(
color: Colors.blue,
),
details: Text("blue"),
),
],
),
The code of such gallery would be :
class Gallery extends StatefulWidget {
final List<GalleryItem> items;
Gallery({#required this.items, Key key})
: assert(items != null),
super(key: key);
#override
_GalleryState createState() => _GalleryState();
}
class _GalleryState extends State<Gallery> {
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Expanded(
child: PageView(
children: widget.items.map((item) => item.content).toList(),
controller: pageController,
),
),
RaisedButton(
onPressed: showContentDetails,
child: Text("More info"),
)
],
);
}
void showContentDetails() {
final index = pageController.page.round();
if (widget.items[index]?.details != null) {
showDialog(
context: context,
builder: (_) =>
GalleryItemDetails(details: widget.items[index].details),
);
}
}
}
class GalleryItemDetails extends StatelessWidget {
final Widget details;
GalleryItemDetails({#required this.details, Key key})
: assert(details != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Dialog(
child: details,
);
}
}
What are the ways to change a property of a class from outside of the class in flutter?
This is a code for BackDrop layout written in dart for Flutter and I want to close the front panel from the class FrontPanel(and that property is for another class, as you see BackPanel class).
I have tried GlobalKey and Setter method to change that property but I couldn't.
How Can I do that?
Regards.
import 'package:flutter/material.dart';
import 'backdrop.dart';
class SimpleExample extends StatelessWidget {
#override
Widget build(BuildContext context) =>
Scaffold(body: SafeArea(child: Panels()));
}
class Panels extends StatelessWidget {
final frontPanelVisible = ValueNotifier<bool>(false);
#override
Widget build(BuildContext context) {
return Backdrop(
frontLayer: FrontPanel(),
backLayer: BackPanel(
frontPanelOpen: frontPanelVisible,
),
frontHeader: FrontPanelTitle(),
panelVisible: frontPanelVisible,
frontPanelOpenHeight: 40.0,
frontHeaderHeight: 48.0,
frontHeaderVisibleClosed: true,
);
}
}
class FrontPanelTitle extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 16.0, left: 16.0),
child: Text(
'Tap Me',
style: Theme.of(context).textTheme.subhead,
),
);
}
}
class FrontPanel extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).cardColor,
child: Center(child: Text('Hello world')));
}
}
class BackPanel extends StatefulWidget {
BackPanel({#required this.frontPanelOpen});
final ValueNotifier<bool> frontPanelOpen;
#override
createState() => _BackPanelState();
}
class _BackPanelState extends State<BackPanel> {
bool panelOpen;
#override
initState() {
super.initState();
panelOpen = widget.frontPanelOpen.value;
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
void _subscribeToValueNotifier() =>
setState(() => panelOpen = widget.frontPanelOpen.value);
/// Required for resubscribing when hot reload occurs
#override
void didUpdateWidget(BackPanel oldWidget) {
super.didUpdateWidget(oldWidget);
oldWidget.frontPanelOpen.removeListener(_subscribeToValueNotifier);
widget.frontPanelOpen.addListener(_subscribeToValueNotifier);
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(
top: 10.0,
),
child: Text('Front panel is ${panelOpen ? "open" : "closed"}'),
)),
Center(
child: RaisedButton(
child: Text('Tap Me'),
onPressed: () {
widget.frontPanelOpen.value = true;
},
)),
// will not be seen; covered by front panel
Center(child: Text('Bottom of Panel')),
]);
}
}
Actually found the answer .
You would be able to do that by making an animation controller and pass the controller to the FrontPages statefulwidget and then using that in the StateClass by widget..(value: newValue)
I have a list of stateful widgets where the user can add, remove, and interact with items in the list. Removing items from the list causes subsequent items in the list to rebuild as they shift to fill the deleted row. This results in a loss of state data for these widgets - though they should remain unaltered other than their location on the screen. I want to be able to maintain state for the remaining items in the list even as their position changes.
Below is a simplified version of my app which consists primarily of a list of StatefulWidgets. The user can add items to the list ("tasks" in my app) via the floating action button or remove them by swiping. Any item in the list can be highlighted by tapping the item, which changes the state of the background color of the item. If multiple items are highlighted in the list, and an item (other than the last item in the list) is removed, the items that shift to replace the removed item lose their state data (i.e. the background color resets to transparent). I suspect this is because _taskList rebuilds since I call setState() to update the display after a task is removed. I want to know if there is a clean way to maintain state data for the remaining tasks after a task is removed from _taskList.
void main() => runApp(new TimeTrackApp());
class TimeTrackApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Time Tracker',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new TimeTrackHome(title: 'Task List'),
);
}
}
class TimeTrackHome extends StatefulWidget {
TimeTrackHome({Key key, this.title}) : super(key: key);
final String title;
#override
_TimeTrackHomeState createState() => new _TimeTrackHomeState();
}
class _TimeTrackHomeState extends State<TimeTrackHome> {
TextEditingController _textController;
List<TaskItem> _taskList = new List<TaskItem>();
void _addTaskDialog() async {
_textController = TextEditingController();
await showDialog(
context: context,
builder: (_) => new AlertDialog(
title: new Text("Add A New Task"),
content: new TextField(
controller: _textController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter the task name'),
),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text("CANCEL")),
new FlatButton(
onPressed: (() {
Navigator.pop(context);
_addTask(_textController.text);
}),
child: const Text("ADD"))
],
));
}
void _addTask(String title) {
setState(() {
// add the new task
_taskList.add(TaskItem(
name: title,
));
});
}
#override
void initState() {
_taskList = List<TaskItem>();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Align(
alignment: Alignment.topCenter,
child: ListView.builder(
padding: EdgeInsets.all(0.0),
itemExtent: 60.0,
itemCount: _taskList.length,
itemBuilder: (BuildContext context, int index) {
if (index < _taskList.length) {
return Dismissible(
key: ObjectKey(_taskList[index]),
onDismissed: (direction) {
if(this.mounted) {
setState(() {
_taskList.removeAt(index);
});
}
},
child: _taskList[index],
);
}
}),
),
floatingActionButton: new FloatingActionButton(
onPressed: _addTaskDialog,
tooltip: 'Click to add a new task',
child: new Icon(Icons.add),
),
);
}
}
class TaskItem extends StatefulWidget {
final String name;
TaskItem({Key key, this.name}) : super(key: key);
TaskItem.from(TaskItem other) : name = other.name;
#override
State<StatefulWidget> createState() => new _TaskState();
}
class _TaskState extends State<TaskItem> {
static final _taskFont =
const TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold);
Color _color = Colors.transparent;
void _highlightTask() {
setState(() {
if(_color == Colors.transparent) {
_color = Colors.greenAccent;
}
else {
_color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Material(
color: _color,
child: ListTile(
title: Text(
widget.name,
style: _taskFont,
textAlign: TextAlign.center,
),
onTap: () {
_highlightTask();
},
),
),
Divider(
height: 0.0,
),
]);
}
}
I ended up solving the problem by creating an intermediate class which contains a reference to the StatefulWidget and transferred over all the state variables. The State class accesses the state variables through a reference to the intermediate class. The higher level widget that contained and managed a List of the StatefulWidget now access the StatefulWidget through this intermediate class. I'm not entirely confident in the "correctness" of my solution as I haven't found any other examples of this, so I am still open to suggestions.
My intermediate class is as follows:
class TaskItemData {
// StatefulWidget reference
TaskItem widget;
Color _color = Colors.transparent;
TaskItemData({String name: "",}) {
_color = Colors.transparent;
widget = TaskItem(name: name, stateData: this,);
}
}
My StatefulWidget and its corresponding State classes are nearly unchanged, except that the state variables no longer reside in the State class. I also added a reference to the intermediate class inside my StatefulWidget which gets initialized in the constructor. Previous uses of state variables in my State class now get accessed through the reference to the intermediate class. The modified StatefulWidget and State classes is as follows:
class TaskItem extends StatefulWidget {
final String name;
// intermediate class reference
final TaskItemData stateData;
TaskItem({Key key, this.name, this.stateData}) : super(key: key);
#override
State<StatefulWidget> createState() => new _TaskItemState();
}
class _TaskItemState extends State<TaskItem> {
static final _taskFont =
const TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold);
void _highlightTask() {
setState(() {
if(widget.stateData._color == Colors.transparent) {
widget.stateData._color = Colors.greenAccent;
}
else {
widget.stateData._color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Material(
color: widget.stateData._color,
child: ListTile(
title: Text(
widget.name,
style: _taskFont,
textAlign: TextAlign.center,
),
onTap: () {
_highlightTask();
},
),
),
Divider(
height: 0.0,
),
]);
}
}
The widget containing the List of TaskItem objects has been replaced with a List of TaskItemData. The ListViewBuilder child now accesses the TaskItem widget through the intermediate class (i.e. child: _taskList[index], has changed to child: _taskList[index].widget,). It is as follows:
class _TimeTrackHomeState extends State<TimeTrackHome> {
TextEditingController _textController;
List<TaskItemData> _taskList = new List<TaskItemData>();
void _addTaskDialog() async {
_textController = TextEditingController();
await showDialog(
context: context,
builder: (_) => new AlertDialog(
title: new Text("Add A New Task"),
content: new TextField(
controller: _textController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter the task name'),
),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text("CANCEL")),
new FlatButton(
onPressed: (() {
Navigator.pop(context);
_addTask(_textController.text);
}),
child: const Text("ADD"))
],
));
}
void _addTask(String title) {
setState(() {
// add the new task
_taskList.add(TaskItemData(
name: title,
));
});
}
#override
void initState() {
_taskList = List<TaskItemData>();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Align(
alignment: Alignment.topCenter,
child: ListView.builder(
padding: EdgeInsets.all(0.0),
itemExtent: 60.0,
itemCount: _taskList.length,
itemBuilder: (BuildContext context, int index) {
if (index < _taskList.length) {
return Dismissible(
key: ObjectKey(_taskList[index]),
onDismissed: (direction) {
if(this.mounted) {
setState(() {
_taskList.removeAt(index);
});
}
},
child: _taskList[index].widget,
);
}
}),
),
floatingActionButton: new FloatingActionButton(
onPressed: _addTaskDialog,
tooltip: 'Click to add a new task',
child: new Icon(Icons.add),
),
);
}
}
I've the below custom widget that make a Switch and reads its status (true/false)
Then I add this one to my main app widget (parent), how can I make the parent knows the value of the switch!
import 'package:flutter/material.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var myvalue = true;
void onchange(bool value) {
setState(() {
this.myvalue = value; // I need the parent to receive this one!
print('value is: $value');
});
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("Enable/Disable the app in the background",
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,),
new Switch(value: myvalue, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
In the main.dart (parent) file, I started with this:
import 'widgets.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepOrange,
),
home: new MyHomePage(title: 'My App settup'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget e = new Switchy();
//...
}
The first possibility is to pass a callback into your child, and the second is to use the of pattern for your stateful widget. See below.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// note: updated as context.ancestorStateOfType is now deprecated
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
There is also the alternative of using an InheritedWidget instead of a StatefulWidget; this is particularly useful if you want your child widgets to rebuild if the parent widget's data changes and the parent isn't a direct parent. See the inherited widget documentation
In 2020, the function in the highest voted answer is marked deprecated. So here is the modified solution based on that answer.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// --> NOTE this! <--
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
However, the methods mentioned in the answers of this question has a drawback. From doc:
In general, though, consider using a callback that triggers a stateful change in the ancestor rather than using the imperative style implied by this method. This will usually lead to more maintainable and reusable code since it decouples widgets from each other.
Calling this method is relatively expensive (O(N) in the depth of the tree). Only call this method if the distance from this widget to the desired ancestor is known to be small and bounded.
I think notifications are quite a civilized solution and they allow for a very clean communication without variable juggling and they bubble up if you need them to:
Define a notification:
class SwitchChanged extends Notification {
final bool val
SwitchChanged(this.val);
}
Raise notification in your child's event handler:
onPressed: () {
SwitchChanged(true).dispatch(context);
}
Finally, wrap your parent with notification listener:
NotificationListener<SwitchChanged>(
child: YourParent(...),
onNotification: (n) {
setState(() {
// Trigger action on parent via setState or do whatever you like.
});
return true;
}
)
You can pass a callback defined in the parent widget to the child widget and as soon as an action is performed in the child widget, the callback gets invoked.
class ParentWidget extends StatelessWidget {
// This gets called when the button is pressed in the ChildWidget.
void _onData(String data) {
print(data); // Hello World
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChildWidget(onData: _onData),
);
}
}
class ChildWidget extends StatelessWidget {
final void Function(String) onData;
ChildWidget({
super.key,
required this.onData,
});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Pass 'Hello World' to parent widget.
onData('Hello World');
},
child: Text('Button'),
);
}
}
Use InheritedWidget - https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
This lets you access data of the parent in all the children
I found a way to do this which was fairly simple, I'm a flutter noob so maybe it isn't the best way. If someone sees something wrong with it, feel free to leave a comment. Basically state is set in parent widget, child widget updates the state of the parent, and any child widgets of the parents which use the state values are redrawn when the value is updated.
Parent widget:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _stringToChange = ""; // the string you want to update in child
// function to update state with changes to term
_updateStringToChange(String stringToChange) {
setState(() {
_stringToChange = stringToChange;
// Other logic you might want to do as string value changes
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: const Center(
child: Text("app bar title"),
),
),
body: Column(children: <Widget>[
ChildWhichMakesChanges(
updateStringToChange: _updateStringToChange,
),
Expanded(
child: Container(
padding: const EdgeInsets.fromLTRB(20, 10, 0, 10),
child: ChildWhichUsesChanges(
stringToChange: _stringToChange,
)))
]),
));
}
}
ChildWhichMakesChanges (this example uses a text box to enter input):
class ChildWhichMakesChanges extends StatefulWidget {
final ValueChanged<String> updateStringToChange;
const ChildWhichMakesChanges({Key? key, required this.updateStringToChange}) : super(key: key);
#override
_TextInputState createState() => _TextInputState();
}
class _TextInputState extends State<ChildWhichMakesChanges> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 25),
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
),
onChanged: (String stringToChange) {
widget.updateStringToChange(stringToChange);
})),
]);
}
}
Using the changed string value in ChildWhichUsesChanges:
class ChildWhichUsesChanges extends StatelessWidget {
final String stringToChange;
const ChildWhichUsesChanges(
{Key? key,
required this.stringToChange})
: super(key: key);
#override
Widget build(BuildContext context) {
return Text(stringToChange)
}
}
2022 Solution:
A simple one.
Make it work like interface.
You can make your own custom CallBack Function just by defining typedef. It will just work as an interface between child to parent widget.
This is an IMP function:
typedef void GetColor(Color? color, String? string);
Following is Parent Widget:
import 'package:flutter/material.dart';
typedef void GetColor(Color? color, String? string);
class NavigationDialog extends StatefulWidget {
const NavigationDialog({Key? key}) : super(key: key);
#override
_NavigationDialogState createState() => _NavigationDialogState();
}
class _NavigationDialogState extends State<NavigationDialog> {
Color? color = Colors.blue[700];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
title: const Text('Navigation Dialog Screen'),
),
body: Center(
child: ElevatedButton(
child: const Text('Change Color'),
onPressed: () {
_showColorDialog(context, (value, string) {
setState(() {
color = value;
print(string);
});
});
}),
),
);
}
And Following is a child Widget Code:
_showColorDialog(BuildContext context, Function getColor) async {
color = null;
await showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Very important question'),
content: const Text('Please choose a color'),
actions: <Widget>[
TextButton(
child: const Text('Red'),
onPressed: () {
color = Colors.red[700];
getColor(color, 'Red');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Green'),
onPressed: () {
color = Colors.green[700];
getColor(color, 'Green');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Blue'),
onPressed: () {
color = Colors.blue[700];
getColor(color, 'Blue');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
],
);
},
);
}
}
In this example, We are selecting a color from Child Alert Dialog widget and pass to Parent widget.
Store the value in that child widget in shared preference, then access that shared preference value in the parent widget.