121

I am having a stateful widget and having some functions inside that stateful widget under the class that extends State<MusicPlayer>

file lib\main.dart

just take a simple function like

class MyAppState extends State<MyApp>{
...
void printSample (){
  print("Sample text");
}
...

this function is inside the stateful widget inside main class .

there is another file lib\MyApplication.dart

this file also has a stateful widget can I do something so that I can call the function printSample() here ..

class MyApplicationState extends State<MyApplication>{
...
@override
  Widget build(BuildContext context) {
    return new FlatButton(
      child: new Text("Print Sample Text"),
      onPressed :(){
       // i want to cal the function here how is it possible to call the 
       // function 
       // printSample()  from here??  
      }
    );
  }
}
Community
  • 1
  • 1
Aman Malhotra
  • 3,068
  • 2
  • 18
  • 28
  • Possible duplicate of [Trigger a function from a widget to a State object](https://stackoverflow.com/questions/50733840/trigger-a-function-from-a-widget-to-a-state-object) – Rémi Rousselet Jun 25 '18 at 18:22
  • If you can ensure one widget is a descendant of another, you can use [InheritedWidget](https://docs.flutter.io/flutter/widgets/InheritedWidget-class.html) – Jacob Phillips Jun 25 '18 at 18:27
  • @JacobPhillips Just use `context.ancestorStateOfType` in that situation – Rémi Rousselet Jun 25 '18 at 18:31
  • @RémiRousselet I need Just a little more help here i am trying to send data from a child widget to a parent widget and all the examples of stream and listenable do the opposite and thats what exactly my problem is . I want to call a function from say a child widget to a parent widget sorry for not mentioning that above .. – Aman Malhotra Jun 25 '18 at 19:58
  • 1
    Pass the entire `StreamController` to your child instead of just `Stream` if your child should submit events too. – Rémi Rousselet Jun 25 '18 at 20:01

9 Answers9

137

To call a function of a parent, you can use the callback pattern. In this example, a function (onColorSelect) is passed to the child. The child calls the function when a button is pressed:

import 'package:flutter/material.dart';

class Parent extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return ParentState();
  }
}

class ParentState extends State<Parent> {
  Color selectedColor = Colors.grey;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          color: selectedColor,
          height: 200.0,
        ),
        ColorPicker(
          onColorSelect: (Color color) {
            setState(() {
              selectedColor = color;
            });
          },
        )
      ],
    );
  }
}

class ColorPicker extends StatelessWidget {
  const ColorPicker({this.onColorSelect});

  final ColorCallback onColorSelect;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        RaisedButton(
          child: Text('red'),
          color: Colors.red,
          onPressed: () {
            onColorSelect(Colors.red);
          },
        ),
        RaisedButton(
          child: Text('green'),
          color: Colors.green,
          onPressed: () {
            onColorSelect(Colors.green);
          },
        ),
        RaisedButton(
          child: Text('blue'),
          color: Colors.blue,
          onPressed: () {
            onColorSelect(Colors.blue);
          },
        )
      ],
    );
  }
}

typedef ColorCallback = void Function(Color color);

Internal Flutter widgets like buttons or form fields use exactly the same pattern. If you only want to call a function without any arguments, you can use the VoidCallback type instead defining your own callback type.


If you want to notify a higher up parent, you can just repeat this pattern on every hierarchy level:

class ColorPickerWrapper extends StatelessWidget {
  const ColorPickerWrapper({this.onColorSelect});
  
  final ColorCallback onColorSelect;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.all(20.0),
      child: ColorPicker(onColorSelect: onColorSelect),
    )
  }
}

Calling a method of child widget from a parent widget is discouraged in Flutter. Instead, Flutter encourages you to pass down the state of a child as constructor parameters. Instead of calling a method of the child, you just call setState in the parent widget to update its children.


One alternative approach are the controller classes in Flutter (ScrollController, AnimationController, ...). These are also passed to the children as constructor parameters, and they contain methods to control the state of the child without calling setState on the parent. Example:

scrollController.animateTo(200.0, duration: Duration(seconds: 1), curve: Curves.easeInOut);

The children are then required to listen to these changes to update their internal state. Of course, you can also implement your own controller class. If you need to, I recommend you to look at the source code of Flutter to understand how that works.


Futures and streams are another alternative to pass down state, and could also be used to call a function of a child.

But I really don't recommend it. If you need to call a method of a child widget, it is very like that your application architecture is flawed. Try to move the state up to the common ancestor!

gotnull
  • 26,454
  • 22
  • 137
  • 203
boformer
  • 28,207
  • 10
  • 81
  • 66
  • 1
    "Calling a method of child widget from a parent widget is discouraged" is it discouraged even if called via a GlobalKey?? – Mohamed Ali Mar 02 '19 at 22:42
  • 5
    Bit Complicated !! – jrh Mar 11 '19 at 11:15
  • yes, thanks! this was a life saver. I had a large dialog box called from a floating action button, with lots of local dropdowns defined and did NOT want to copy/paste that but wanted the same floating action button in the child stateful page as well. This was exactly what I was looking for. – Eradicatore Sep 17 '19 at 03:32
  • @boformer If I want to define these classes in different classes, ColorPicker in one dart file and (Parent, ParentState ) in different class then where should I define typedef ColorCallback as I tried and it was failing –  Dec 16 '19 at 02:44
  • Bravo, I have learned so much from this answer and it help me avoid some pitfall from building Flutter app. Thank you boformer ! – CharlesC Jul 06 '20 at 19:22
  • Perfectly explained ! – abhijat_saxena Aug 31 '20 at 20:50
  • Good answer but OP asked for an example with a Stateful Widget, which will be different than your answer. – Richard Bonneau Oct 13 '20 at 23:12
  • I think that the OP was looking for a solution, and he provided all the possible choices. – funder7 Dec 23 '20 at 11:56
  • 1
    what if the widget is not a descended but a separate class that was imported, In my case it is a `Block` widget which is just a colored container, used in visualising sorting algorithm, I want to change the colour when that block is the key (Insertion sort). then how do you do it? Sorry if this question is already answered in your answer, and I failed to get it. – Mrak Vladar Mar 18 '21 at 13:10
85

You can do that by use key of widget

myWidget.dart

    class MyWidget extends StatefulWidget {
     
      const MyWidget ({Key key}) : super(key: key);
     
      @override
      State<StatefulWidget> createState()=> MyState();
    
  
    }

   class MyState extends State<MyWidget >{
        
          
               
      Widget build(BuildContext context){ return ....}
    
      void printSample (){
         print("Sample text");
      }
        
 }

now when use MyWidget declare GlobalKey as global key

  GlobalKey<MyState> _myKey = GlobalKey();

and pass it when create widget

MyWidget(
key : _myKey,
)

by this key you can call any public method inside state

_myKey.currentState.printSample();
Mahmoud Abu Alheja
  • 3,343
  • 2
  • 24
  • 41
18

if you want to call printSample() func you can use:

class Myapp extends StatefulWidget{
...
    final MyAppState myAppState=new MyAppState();
    @override
    MyappState createState() => MyAppState();
    void printSample(){
        myAppState.printSample();
    }
}
class MyAppState extends State<MyApp>{
    void printSample (){
        print("Sample text");
    }
}

...............
Myapp _myapp = new Myapp();
_myapp.printSample();
...
Shaan Mephobic
  • 1,148
  • 2
  • 7
  • 15
mkf
  • 496
  • 3
  • 8
  • 13
    I don't think this is valid for a more complex case: The Myapp StatefulWidget can be constructed repeatedly and therefore the state that any given instance holds may not be the same state object that Flutter holds and associates with the tree, right? – Pat Niemeyer Apr 02 '19 at 22:50
  • Hello @PatNiemeyer , Is this right? _StatefulButtonState _lastState; //override State createState() { _lastState = new _StatefulButtonState(onPressed: onPressed); return _lastState; } – BuffK Nov 28 '19 at 07:36
  • This isn't a good method because: 1. You have the state instance for the method execution and another one for the flutter tree. 2. The state can be created multiple times over the widget lifecycle. – Marius Van Nieuwenhuyse Nov 21 '22 at 10:17
10

You can give this a try, it will call a method defined in Page2 (StatefulWidget) from Page1 (StatefulWidget) widget.

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: RaisedButton(
          child: Text("Call page 2 method"),
          onPressed: () => Page2().method(),
        ),
      ),
    );
  }
}

class Page2 extends StatefulWidget {
  method() => createState().methodInPage2();

  @override
  _Page2State createState() => _Page2State();
}

class _Page2State extends State<Page2> {
  methodInPage2() => print("method in page 2");

  @override
  Widget build(BuildContext context) => Container();
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 2
    This answer is exactly the same as Mehmet Akif BAYSAL's answer (concept is 100% the same). Mehmet Akif BAYSAL's answer is just more eloquently illustrated in the code example and thus much easier to understand. – SilSur Aug 01 '19 at 21:40
  • If I want to call a method in bloc, what is the correct way? https://stackoverflow.com/questions/62097660/call-a-method-from-another-class-flutter – Tony May 30 '20 at 04:07
9

While using callbacks and GlobalKey's is fine for simple use cases, for more complex setups they should probably be considered anti-patterns given that they hook into widget types and rely on low-level implementation logic.

If you find yourself adding more and more callbacks/globalkeys, and it starts to get messy, then it might be time to switch to something like StreamController + StreamSubscription. This way you can decouple your events from the specific widget types and abstract away the inter-widget communication logic.

Register an event controller

In your top-level widget (app-level or page-level depending on your needs) create the StreamController instance, and make sure you release it in the dispose() method:

class _TopLevelPageState extends State<TopLevelPage> {
  StreamController<MyCustomEventType> eventController = StreamController<MyCustomEventType>.broadcast();

// ...
  @override
  void dispose() {
    eventController.close();
    super.dispose();
  }
}

Pass the eventController instance as a constructor argument down into any child widget that needs to listen to events and/or trigger events.

MyCustomEventType can be either an enum (if you don't need to pass extra data) or a regular object with whatever fields you need in case you need to set extra data on the event.

Trigger an event

Now in any of the widgets (including the parent one, where you declared the StreamController) you can trigger events with:

eventController.sink.add(MyCustomEventType.UserLoginIsComplete);

Listen to events

To set up a listener in your child (or parent widget) put the following code in initState():


class _ChildWidgetState extends State<ChildWidget> {
  @override
  void initState() {
    super.initState();
    // NOTE: 'widget' is the ootb reference to the `ChildWidget` instance. 
    this.eventSubscription = widget.eventController.stream.asBroadcastStream().listen((event) {
      if (event == MyCustomEventType.UserLoginIsComplete) {
        print('handling LOGIN COMPLETE event ' + event.toString());
      } else {
        print('handling some other event ' + event.toString());
      }
  }

  @override
  void dispose() {
    this.parentSubscription.cancel();
    super.dispose();
  }
}

Be aware that if you override StreamController.done() then your listeners will not fire because done() replaces whatever listeners you have set previously.


NOTE: If you have a one-to-one communication relationship between two widgets then you do not need the broadcast events flavour — in that case you can create the controller without .broadcast(), i.e. with StreamController<MyCustomEventType>() and to listen instead of .stream.asBroadcastStream().listen() you can use .stream.listen(). Also see https://api.dart.dev/stable/dart-async/Stream-class.html

For an answer which outlines this and other approaches see Inter Widget communication

ccpizza
  • 28,968
  • 18
  • 162
  • 169
5

I found another solution by trial-and-error, but it worked.

import 'main.dart' as main;

Then add this line under the onPressed.

main.MyAppState().printSample();
zinger
  • 320
  • 2
  • 9
0

Here HomePage is parent page and ChildPage is child page. There is one method called onSelectItem, which we need to call from child page.

class HomePage extends StatefulWidget {

  @override HomePageState createState() => HomePageState();
}

class HomePageState extends State<HomePage> {

  onSelectItem(String param) {
    print(param);
  }

  @override Widget build(BuildContext context) {

  }
}

class ChildPage extends StatefulWidget {
  final HomePageState homePageState;

  ChildPage({Key key, @required this.homePageState}) : super(key: key);

  _ChildPageState createState() => _ChildPageState();
}

class _ChildPageState extends State<ChildPage> {
  @override Widget build(BuildContext context) {

    return RaisedButton(
      onPressed: () {
        widget.homePageState.onSelectItem("test");
      },
      child: const Text(
          'Click here',
          style: TextStyle(fontSize: 20)
      ),
    );
  }
}

So, by using the widget and parent class state we can call the parent class method.

apurv thakkar
  • 8,608
  • 3
  • 14
  • 19
0

Well I have come up with the simple Solution

Step 1 -- Define a Child widget

    class MyChildWidget extends StatelessWidget {
      final Function detailsButtonFunction;
        MyChildWidget(
         {Key? key,
         required this.detailsButtonFunction,
       })
      : super(key: key);

   @override
     Widget build(BuildContext context) {
       return  Container(
          child: SingleChildScrollView(
            child: Container(
              child: ElevatedButton(
                  child: Text("Some Sample text")
                     onPressed : (){
                     detailsButtonFunction()
                   } 
                )
              ),
            ),
          ),
         );
        }
       }

Step 2- Place it into the Parent widget

  class MyparentWidget extends StatelessWidget {

  MyParentWidget({Key? key}): super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
     
        child: SingleChildScrollView(
          child: Container(
            child: Column(
              children:[ MyChildWidget(detailsButtonFunction:(){
                print("something")
                }  ),]
            ),
          ),
        ),
      ),
    );
  }
}

final Note -- Formatting and spelling may have some mistakes, Just focus on the function part.. when we pass we do like this (){Print("someThing")}, when we receive we do like this (){functioname()}.... adding brackets are very important

pmatatias
  • 3,491
  • 3
  • 10
  • 30
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – mohammad mobasher Oct 19 '22 at 08:03
0

Call a method from another class or how to setState a page from outside that class, using StreamController.

import 'package:flutter/material.dart';
import 'dart:async';

StreamController<int> streamController = StreamController<int>();

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage('Flutter Demo Home Page', streamController.stream),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage(this.title, this.stream);
  final String title;
  final Stream<int> stream;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String menuName = 'A';

  @override
  void initState() {
    super.initState();
    widget.stream.listen((index) {
      mySetState(index);
    });
  }

  void mySetState(int index) {
    List menuList = ['A', 'B', 'C'];
    setState(() {
      menuName = menuList[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: Text(
                'Today\'s special is:',
                style: TextStyle(fontSize: 20),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(10.0),
              child: Text(
                'Combo ' + menuName,
                style: TextStyle(fontSize: 30, color: Colors.blue),
              ),
            ),
            SizedBox(
              height: 50,
            ),
            ElevatedButton(
              style: ButtonStyle(
                backgroundColor:
                    MaterialStateProperty.all<Color>(Colors.blue[700]),
              ),
              child: Text(
                'Settings',
                style: TextStyle(color: Colors.white),
              ),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) => SecondPage()),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

class SecondPage extends StatefulWidget {
  @override
  _SecondPageState createState() => _SecondPageState();
}

class _SecondPageState extends State<SecondPage> {
  List<bool> isSelected = [true, false, false];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(30.0),
              child: Text(
                'Select a menu name:',
                style: TextStyle(fontSize: 20),
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(20.0),
              child: ToggleButtons(
                children: <Widget>[
                  Text('A'),
                  Text('B'),
                  Text('C'),
                ],
                onPressed: (int index) {
                  setState(() {
                    for (int buttonIndex = 0;
                        buttonIndex < isSelected.length;
                        buttonIndex++) {
                      if (buttonIndex == index) {
                        isSelected[buttonIndex] = true;
                      } else {
                        isSelected[buttonIndex] = false;
                      }
                    }
                  });
                  streamController.add(index);
                },
                isSelected: isSelected,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
ArNo
  • 2,278
  • 1
  • 22
  • 19