Hasan A Yousef
Hasan A Yousef

Reputation: 24918

How to pass data from child widget to its parent

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();
  //...

}

Upvotes: 82

Views: 96187

Answers (9)

rmtmckenzie
rmtmckenzie

Reputation: 40423

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

Upvotes: 102

Marcin
Marcin

Reputation: 4289

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

Upvotes: 21

androidEnthusiast
androidEnthusiast

Reputation: 1160

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"); 

}

}}

Upvotes: 1

CopsOnRoad
CopsOnRoad

Reputation: 267404

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

Upvotes: 12

RonManning
RonManning

Reputation: 137

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

Upvotes: 1

Sagar Shah
Sagar Shah

Reputation: 4292

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.

Upvotes: 0

The_AK47
The_AK47

Reputation: 9

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

Upvotes: -3

ch271828n
ch271828n

Reputation: 17567

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.

Upvotes: 20

ProgrammingWithYash
ProgrammingWithYash

Reputation: 73

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

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

Upvotes: 2

Related Questions