83

I have the below custom widget that makes 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(); //... } 
1
  • 4
    This would be a hell of a lot easier if flutter provided a context.parent property. Commented Jan 11, 2020 at 9:59

9 Answers 9

102

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

Sign up to request clarification or add additional context in comments.

11 Comments

@Remi Look, I think you're missing the point of what he was asking. He has data in a child, that he wants to get to a parent. Whether that's done using a callback, or using of, or any other method, it's passing data up the tree. You can call it something else, but that's what it is.
I can call it 'send data to parent' or 'callback data to parent' if that makes you happy - I know that it isn't expressly passed as an argument. But before the call there is a string that only the child has, and afterwards the parent has it. You can argue that it isn't passed in the method sense, but to all of us who don't care that much about the semantics some information moves and therefore was passed in the broader sense of the word. Arguing that it wasn't passed, while maybe semantically more correct, is a little counterproductive....
Yes. But in the end, the question was "How to pass data from child to parent". And the answer didn't do that. So while it's valid, it's important to explain why and use the correct terms.
@csguy rather than deleting an answer, it's generally better to just suggest an edit. I've updated the answer to remove the deprecated function.
@RémiRousselet based on the upvotes and that the user accepted this answer, I would argue your distinction is not important and adds nothing to this answer.
|
22

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 child with notification listener:

NotificationListener<SwitchChanged>( child: YourChildWidget(...), onNotification: (n) { setState(() { // Trigger action on parent via setState or do whatever you like. }); return true; } ) 

3 Comments

This is probably the simplest way to send any data up the widget tree no matter how far you need to go.
This is probably a better solution IMO. For those looking for a detailed answer (along with some other options) - flutteragency.com/send-data-from-child-widget-to-its-parent
Shouldn't the line NotificationListener<ItemChangedNotification>( be NotificationListener<SwitchChanged>( ?
20

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.

3 Comments

What if child MyChildClass is stateful widget?
@FaizanKamal No any change
This seems to work just fine. Is there any reason one shouldn't use or depend on this?
12

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

2 Comments

The OP asked about passing data up the widget hierarchy of a particular screen, not about data between screens.
@PeteAlvin I think you misunderstood it. The data isn't passed using Navigator.pop(...) but through a callback. I have updated the answer to make it more clear.
2

Use InheritedWidget - https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html

This lets you access data of the parent in all the children

1 Comment

But does it let you modify that data in the parent?
1

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

Comments

1

To pass the data from child to parent, I want to contribute with the literally SIMPLEST way that I have used for example when creating custom buttons, and other callback widgets, that I used in the apps. So (1) first example is having the child StatelessWidget and (2) is second example with having the child StatefulWidget, and wanting to pass the data to the parent. Let's start with the StatelessWidget: (1)

 class ChildWidget extends StatelessWidget { //Function can pass the data that you need, // as final void Function(String) onValueSelected - passing some string value, // or final void Function(YourCustomObject) onValueSelected - passing some of yours custom objects, or array, etc.; //or without the params as here: final void Function() onCustomButtonPressed; const ChildWidget({ Key? key, required this.onCustomButtonPressed, }) : super(key: key); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Column( children: [... ElevatedButton( onPressed: onCustomButtonPressed, ...}} 

Using it in the parent widget as:

 class ParentWidget extends StatelessWidget { ... @override Widget build(BuildContext context) { return Scaffold( ... ChildWidget( onCustomButtonPressed: () => navigateToHomePage(),//or with the params, see below ), ChildWidget( onValueSelected: (value) { // use [value] that you passed from the child })} ...}} 

(2) now a child as a StatefulWidget passing some data to the parent, like this. As an example here is some DropdownWidget passing selected string for example:

class CustomDropdownWidget extends StatefulWidget { final void Function(String) onValueSelected; const CustomDropdownWidget({Key? key, required this.onValueSelected}) : super(key: key); @override State<CustomDropdownWidget> createState() => _CustomDropdownWidgetState(this.onValueSelected); } class _CustomDropdownWidgetState extends State<CustomDropdownWidget> { _CustomDropdownWidgetState(onValueSelected); @override Widget build(BuildContext context) { ... DropdownButton{ onChanged: (String newValue) { widget.onValueSelected(newValue); }, }} 

using it in the parent Widget:

 class ParentWidget extends StatelessWidget { ... @override Widget build(BuildContext context) { return Scaffold( ... CustomDropdownWidget(onValueSelected: (value) { print("passed value is $value"); 

}

}}

Comments

0

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.

3 Comments

There is a mistake in code. You are not using GetColor here. It's just a function. First of all declare typedef right, as a function it is typedef GetColor = void Function(Color? color, String? string);. Secondly.. Just use it) _showColorDialog(BuildContext context, GetColor getColor)
@NikitaShadkovYours is a different way of representation. Otherwise these are production working code examples.
it is not a "different representation" you just basically NOT using variable you've declared)
-3

Store the value in that child widget in shared preference, then access that shared preference value in the parent widget.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.