46

Code:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.help),
            onPressed: () {
              // how can I call methodA from here?
            },
          ),
        ),
        body: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }

  void methodA() {}
}

Question: I want to call methodA() from onPressed() of IconButton. How can I do that, I know how to call parent's method from child class but this use case is different.

  • You can pass a GlobalKey to `HomePage(...)` and use this key to access `HomePage`'s state. It's better to use some event propagation mechanism though (BloC and redux help here but there are many ways to do that) – Günter Zöchbauer Dec 10 '18 at 10:41
  • @GünterZöchbauer Thanks for the hint, I know `ScopedModel`, can that also help here? IMO, that won't help because that is used to pass the data down the tree. –  Dec 10 '18 at 11:15
  • Never had a closer look. You can also use a plain `StreamController` and pass it's stream to the child component and in the child component listen to the stream. In the parent you add to the `StreamController` and the listener (child) calls `methodA` for each event. – Günter Zöchbauer Dec 10 '18 at 12:29
  • @GünterZöchbauer I used `GlobalKey` approach, the code is now working, can you please check if I did it the correct way? –  Dec 10 '18 at 13:42
  • Many thanks @GünterZöchbauer. I have never used `StreamController` so I used `GlobalKey`. –  Dec 10 '18 at 13:47
  • `GlobalKey` is fine for this use case because there is only one such element in your whole application. Don't use it for example for elements in a `ListView`. `GlobalKey` mentions in the docs that it's expensive. That won't hurt if you create one, but likely does if you create hundreds. There are lots of articles about streams and architecture (BloC, Redux, ...). Definitely worth diving into if you want maintainable code. – Günter Zöchbauer Dec 10 '18 at 13:50
  • @GünterZöchbauer Once again thanks for sharing valuable piece of information regarding `GlobalKey`. I really appreciate it :) –  Dec 10 '18 at 14:13

6 Answers6

78

I think the best practice in this case is using the way that the programmers of the Flutter used themselves!

Bellow code is an example from Flutter source. As you can see there in MyCustomForm widget if you want to access the text property of TextField widget (which is the child of MyCustomForm widget in this example) you need to use its controller like this...

class _MyCustomFormState extends State<MyCustomForm> {
  ...
  final myController = TextEditingController();
  ...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      ...
      body: TextField(
        controller: myController,
      ),// TextField
      ...
      floatingActionButton: FloatingActionButton(
        ...
        onPressed: () {
          print(myController.text);
        },
        ...
      ),// FloatingActionButton
    );
  }
}

Now be inspired by this and also knowing the fact that Flutter pass its object parameters by reference so we need a controller class for our child class to be able to access child's fields and functions through the controller object (like the TextField widget in previous example)...in your case we can do it like this:

class HomePageController {
  void Function() methodA;
}

class MyApp extends StatelessWidget {

  final HomePageController myController = HomePageController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.help),
            onPressed: () {
              myController.methodA();
            },
          ),// IconButton
        ),// AppBar
        body: HomePage(
          controller: myController,
        ),// HomePage
      ),
    );
  }
}

class HomePage extends StatefulWidget {

  final HomePageController controller;

  HomePage({ this.controller });

  @override
  _HomePageState createState() => _HomePageState(controller);
}

class _HomePageState extends State<HomePage> {

  _HomePageState(HomePageController _controller) {
    _controller.methodA = methodA;
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }

  void methodA() {}
}

As can be seen in this code myController's reference passes through HomePage to reach the _HomePageState's constructor and then gets the reference of methodA function... and now methodA is accessible in MyApp class via myController!

Hope it Helps! :))

ehsaneha
  • 1,665
  • 13
  • 8
  • 9
    Looks like most elegant solution to me – mbartn Apr 08 '20 at 23:28
  • 1
    I'm trying to use this solution, but when the widget tree is rebuilt because of notifyListeners() the controller is null. – jwasher Apr 19 '20 at 20:24
  • 1
    @jwasher thank u for your comment... you need to override the didUpdateWidget and reinitialize the variables just like how you did in the constructor... for example in this example: override void didUpdateWidget(ProfileNumbersSection oldWidget) { _controller.methodA = methodA ; super.didUpdateWidget(oldWidget); } – ehsaneha Apr 20 '20 at 05:20
  • 1
    @ehsaneha clean and clever solution! – camillo777 May 07 '20 at 08:57
  • 5
    `Don't put any logic in createState.` https://dart-lang.github.io/linter/lints/no_logic_in_create_state.html – Vlad Jul 23 '21 at 08:07
  • 9
    The correct way to do this is to use ```@override void initState() { super.initState(); widget.controller.methodA = methodA; }``` and remove _HomePageState initializing variables as pointed out by @Vlad – Roddy R Sep 29 '21 at 19:55
  • 1
    @ehsaneha I wish I could give you 10 thumbs ups – SwiftiSwift Jan 24 '22 at 06:23
  • 1
    great answer but should be updated to link the controller in the `initState()` method to avoid putting logic in `createState` as @Vlad pointed out. Roddy R's code works for me! – Elias Johannes Aug 17 '22 at 09:16
  • I don't think the flutter team does it this way, providing `empty` Controllers and assigning the methods in initState – Kristi Jorgji Jun 14 '23 at 14:37
  • thank you for your answer, how can I pass a parameter to methodA ? – woshitom Aug 31 '23 at 12:30
49

Thanks to Günter Zöchbauer for pointing me in right direction. I am using GlobalKey to get the things done. However use GlobalKey with care

GlobalKey<_HomePageState> globalKey = GlobalKey();

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.help),
            onPressed: () {
             globalKey.currentState.methodA();
            },
          ),
        ),
        body: HomePage(key: globalKey),
      ),
    );
  }
}
class HomePage extends StatefulWidget {
  HomePage({Key key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }

  void methodA() {}
}
16

2021

I believe this is the most robust solution using typedef and builder-like widget.

import 'package:flutter/material.dart';

typedef MyBuilder = void Function(BuildContext context, void Function() methodA);

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

class MyApp extends StatelessWidget {
  late void Function() myMethod;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          leading: IconButton(
            icon: Icon(Icons.help),
            onPressed: () {
              // how can I call methodA from here?
              myMethod.call();
            },
          ),
        ),
        body: HomePage(builder: (BuildContext context, void Function() methodA) {
          myMethod = methodA;
        },),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  final MyBuilder builder;

  const HomePage({Key? key, required this.builder}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    widget.builder.call(context, methodA);
    return Text('HomePage');
  }

  void methodA() {
    print('test');
  }
}

Roddy R
  • 1,222
  • 10
  • 10
  • 1
    That is indeed an awesome solution, I'm going to have a look on it. – Naimur Hasan Mar 30 '22 at 12:33
  • 1
    Thanks, this is really great! Does anyone know if there are any negative consequences of putting `widget.builder.call(context, methodA);` inside the build method? Actually I have noticed it doesn't execute the `methodA` when the build method runs so it should be fine. – BeniaminoBaggins Nov 19 '22 at 01:01
11

The above answer did not work for me, instead i just replaced the global key and it worked very well:

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
 return MaterialApp(
  home: Scaffold(
    appBar: AppBar(
      leading: IconButton(
        icon: Icon(Icons.help),
        onPressed: () {
         //call it like this:
         HomePage.globalKey.currentState.methodA();
        },
      ),
    ),
    body: HomePage(),
  ),
);
 }
}


class HomePage extends StatefulWidget {
//Add this key like this:
static final GlobalKey<_HomePageState> globalKey = GlobalKey();
super(key: globalKey);

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

class _HomePageState extends State<HomePage> {
 @override
  Widget build(BuildContext context) {
  return Container();
 }

 void methodA() {}
}

Hope everything is working:)

Bensal
  • 3,316
  • 1
  • 23
  • 35
3

Actually, you can't do that because _HomePageState is private. Delete the _ symbol ahead of HomePageState, so it will be public and you can call methodA() using the HomePageState().methodA();

Alex Adrianov
  • 312
  • 1
  • 8
  • 3
    First, I can do `_HomePageState().methodA()` without having me to delete `_`, second, instantiating `HomePageState` is not a good way to call `methodA()`, cause I will be creating a new state. –  Dec 10 '18 at 10:16
  • Personally I'd prefer Alex's solution. I don't see why it should be downvoted. You can create the HomePage instance in the build method, before returning the MaterialApp. Then you can pass it in the body:, and reference it in the onPressed callback. – FJJ Dec 10 '18 at 13:55
  • 2
    @FJJ As far as I know, you should not create 2 instance of the state. And for the tiny work like this, never!!! –  Dec 11 '18 at 10:43
  • I agree with Alex Adrianov it becomes private when you use '_' – John Mar 05 '20 at 12:10
  • 2
    @John It's "file private" when you use '_'. It means you can access it from another class in same file. But not from other file. – goofy Jan 05 '21 at 13:02
0

Calling parent function can be done by a callback, but calling child function from a parent wouldn't be necessary I think. If your parent widget is StateFull you just need to setState(() {}); to refresh the child widget, why you call child function.

Zia
  • 506
  • 3
  • 20