dennbagas
dennbagas

Reputation: 2973

How to make a reusable PopupMenuButton in Flutter

I want to make a reusable PopupMenuButton in flutter, so i can call that widget and assign the PopupMenuItem dynamically.

Here is what i've done, but it throw a framework error. I am relatively new in flutter.

here is class that supposed to be reusable:

class PopupMenu {
  PopupMenu({@required this.title, @required this.onTap});

  final String title;
  final VoidCallback onTap;
}

class PopupmMenuButtonBuilder {
  setPopup(List<PopupMenu> popupItem) {
    return PopupMenuButton<String>(
      onSelected: (_) {
        popupItem.forEach((item) => item.onTap);
      },
      itemBuilder: (BuildContext context) {
        popupItem.forEach(
          (item) {
            return <PopupMenuItem<String>>[
              PopupMenuItem<String>(
                value: item.title,
                child: Text(
                  item.title,
                ),
              ),
            ];
          },
        );
      },
    );
  }
}

and then i call the widget like this:

child: PopupmMenuButtonBuilder().setPopup([
              PopupMenu(title: 'Item 1', onTap: () => print('item 1 selected')),
              PopupMenu(title: 'Item 2', onTap: () => print('item 2 selected')),
            ]),

It shows the 3 dot icon button, but when i tap the icon it throws this error:

I/flutter ( 8509): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter ( 8509): The following assertion was thrown while handling a gesture:
I/flutter ( 8509): 'package:flutter/src/material/popup_menu.dart': Failed assertion: line 723 pos 10: 'items != null &&
I/flutter ( 8509): items.isNotEmpty': is not true.
I/flutter ( 8509): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 8509): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 8509): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 8509):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 8509): When the exception was thrown, this was the stack:
I/flutter ( 8509): #2      showMenu (package:flutter/src/material/popup_menu.dart:723:10)
I/flutter ( 8509): #3      _PopupMenuButtonState.showButtonMenu (package:flutter/src/material/popup_menu.dart:898:5)
I/flutter ( 8509): #4      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:511:14)
I/flutter ( 8509): #5      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:566:30)
I/flutter ( 8509): #6      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:166:24)
I/flutter ( 8509): #7      TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:240:9)
I/flutter ( 8509): #8      TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:211:7)
I/flutter ( 8509): #9      GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156:27)
I/flutter ( 8509): #10     _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:225:20)
I/flutter ( 8509): #11     _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:199:22)
I/flutter ( 8509): #12     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
I/flutter ( 8509): #13     _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)I/flutter ( 8509): #14     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)I/flutter ( 8509): #18     _invoke1 (dart:ui/hooks.dart:233:10)
I/flutter ( 8509): #19     _dispatchPointerDataPacket (dart:ui/hooks.dart:154:5)
I/flutter ( 8509): (elided 5 frames from class _AssertionError and package dart:async)
I/flutter ( 8509): Handler: onTap
I/flutter ( 8509): Recognizer:
I/flutter ( 8509):   TapGestureRecognizer#8968f(debugOwner: GestureDetector, state: ready, won arena, finalPosition:
I/flutter ( 8509):   Offset(339.0, 54.0), sent tap down)
I/flutter ( 8509): ════════════════════════════════════════════════════════════════════════════════════════════════════

Upvotes: 4

Views: 5303

Answers (2)

Xavier
Xavier

Reputation: 107

You can also create a changeable list to append to the Popup menu instead of manually creating it one by one in the child

child: PopupMenu(popUpList: _popUpList).createPopup()),

then in the Popup menu class

class PopupMenu {
 const PopupMenu({this.popUpList});  
 final List<PopUpList> popUpList; 

 PopupMenuButton<String> createPopup() {
   return PopupMenuButton<String>(
     onSelected: (value) {
       popUpList.firstWhere((e) => e.title == value).onTap();
     },
     itemBuilder: (context) => popUpList
         .map((item) => PopupMenuItem<String>(
       value: item.title,
       child: Text(item.title),
     )).toList(),
   );
  }
}

model

class PopUpList{
  String title;
  VoidCallback onTap;

  PopUpList({this.onTap, this.title});
}

cheers!

Upvotes: 1

Richard Heap
Richard Heap

Reputation: 51682

Your itemBuilder needs to return a List. It doesn't actually return anything - note how the return is inside the forEach so it's just returning from the lambda. In general, forEach should sometimes be avoided. Also, the PopupMenuButtonBuilder class is redundant - it could be replaced with a static or top-level function.

One other thing that's unclear is why you want to call each onTap for every select. As you currently have it it's going to call every callback!

Try this:

class PopupMenu {
  PopupMenu({@required this.title, @required this.onTap});

  final String title;
  final VoidCallback onTap;

  static PopupMenuButton<String> createPopup(List<PopupMenu> popupItems) {
    return PopupMenuButton<String>(
      onSelected: (value) {
        popupItems.firstWhere((e) => e.title == value).onTap();
      },
      itemBuilder: (context) => popupItems
          .map((item) => PopupMenuItem<String>(
                value: item.title,
                child: Text(
                  item.title,
                ),
              ))
          .toList(),
    );
  }
}

Upvotes: 3

Related Questions