Home > OS >  How to pass a GlobalKey through Stateless Widget Children
How to pass a GlobalKey through Stateless Widget Children

Time:11-30

I'm trying to create a custom menu bar in my app. Right now, the biggest issue I'm having is passing a state for when it's expanded to it's children after a setState occurs.

I thought about inheritance, but from what I've tried all inheritance needs to be in-line. I can't create a widget where the children [] are fed into the constructor on an ad-hoc basis.

My current approach is to use a GlobalKey to update the State of the children widgets being inserted into the StateFul while updating them directly.

The children for my MenuBar are declared as:

  List<MenuBarItem> menuItems;

MenuBarItem is an abstract interface class that I intend to use to limit the widgets that can be fed in as menuItems to my MenuBar.

abstract class iMenuItem extends Widget{}
class MenuBarItem extends StatefulWidget implements iMenuItem{

At some iterations of this script, I had a bool isExpanded as part of the iMenuItem, but determined it not necessary.

Here is my code at its current iteration:

My Main:


void main() {
//  runApp(MainApp());
  //runApp(InherApp());
  runApp(MenuBarApp());
}


class MenuBarApp extends StatelessWidget{

  @override
  Widget build(BuildContext context){

    return MaterialApp(
      home: Scaffold(
        body: MenuBar(
          menuItems: [
            // This one does NOT work and is where I'm trying to get the 
            // value to update after a setState
            MenuBarItem(
              myText: 'Outsider',
            ),
          ],
        ),

      ),

    );
  }
}


My Code:


import 'package:flutter/material.dart';



/// Primary widget to be used in the main()
class MenuBar extends StatefulWidget{

  List<MenuBarItem> menuItems;

  MenuBar({
    required this.menuItems,
  });

  @override
  State<MenuBar> createState() => MenuBarState();

}

class MenuBarState extends State<MenuBar>{

  bool isExpanded = false;
  late GlobalKey<MenuBarContainerState> menuBarContainerStateKey;
  @override
  void initState() {
    super.initState();
    menuBarContainerStateKey = GlobalKey();
  }

  @override
  Widget build(BuildContext context){
      return MenuBarContainer(
          menuItems: widget.menuItems,
      );

  }

}


class MenuBarContainer extends StatefulWidget{

  List<MenuBarItem> menuItems;
  late Key key;
  MenuBarContainer({
    required this.menuItems,
        key,
  }):super(key: key);

  @override
  MenuBarContainerState createState() => MenuBarContainerState();

}

class MenuBarContainerState extends State<MenuBarContainer>{

  bool isExpanded =  false;

  @override
  void initState() {
    super.initState();
    isExpanded = false;
  }

  @override
  Widget build(BuildContext context){


    List<Widget> myChildren = [
      ElevatedButton(
        onPressed: (){
          setState((){
            this.isExpanded = !this.isExpanded;
          });
        },
        child: Text('Push Me'),
      ),
      // This one works. No surprise since it's in-line
      MenuBarItem(isExpanded: this.isExpanded, myText: 'Built In'),
    ];

    myChildren.addAll(widget.menuItems);

    return Container(
      child: Column(
        children: myChildren,
      ),
    );
  }
}

/// The item that will appear as a child of MenuBar
/// Uses the iMenuItem to limit the children to those sharing
/// the iMenuItem abstract/interface
class MenuBarItem extends StatefulWidget implements iMenuItem{

  bool isExpanded;
  String myText;
  MenuBarItem({
    key,
    this.isExpanded = false,
    required this.myText,

  }):super(key: key);

  @override
  State<MenuBarItem> createState() => MenuBarItemState();

}

class MenuBarItemState extends State<MenuBarItem>{


  @override
  Widget build(BuildContext context){
    GlobalKey<MenuBarState> _menuBarState;

    return Row(
      children: <Widget> [
        Text('Current Status:\t${widget.isExpanded}'),
        Text('MenuBarState GlobalKey:\t${GlobalKey<MenuBarState>().currentState?.isExpanded ?? false}'),
        Text(widget.myText),
      ],
    );
  }
}

/// To give a shared class to any children that might be used by MenuBar
abstract class iMenuItem extends Widget{

}



I've spent 3 days on this, so any help would be appreciated.

Thanks!!

CodePudding user response:

I suggest using ChangeNotifier, ChangeNotifierProvider, Consumer and context.read to manage state. You have to add this package and this import: import 'package:provider/provider.dart';. The steps:

  1. Set up a ChangeNotifier holding isExpanded value, with a setter that notifies listeners:
class MyNotifier with ChangeNotifier {
  bool _isExpanded = false;
  bool get isExpanded => _isExpanded;
  set isExpanded(bool isExpanded) {
    _isExpanded = isExpanded;
    notifyListeners();
  }
}
  1. Insert the above as a ChangeNotifierProvider in your widget tree at MenuBar:
class MenuBarState extends State<MenuBar> {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
        create: (context) => MyNotifier(),
        child: MenuBarContainer(
          menuItems: widget.menuItems,
        ));
  }
}

After this you can easily read and write the isExpanded value from anywhere in your widget tree under the ChangeNotifierProvider, for example:

ElevatedButton(
  onPressed: () {
    setState(() {
      final myNotifier = context.read<MyNotifier>();
      myNotifier.isExpanded = !myNotifier.isExpanded;
    });
  },
  child: Text('Push Me'),
),

And if you want to use this state to automatically build something when isExpanded is changed, use Consumer, which will be notified automatically upon every change, for example:

class MenuBarItemState extends State<MenuBarItem> {
  @override
  Widget build(BuildContext context) {
    return Consumer<MyNotifier>(builder: (context, myNotifier, child) {
      return Row(
        children: <Widget>[
          Text('Current Status:\t${myNotifier.isExpanded}'),
          Text(widget.myText),
        ],
      );
    });
  }
}

  • Related