Flutter: Close DropdownButton (DropdownMenu)

5.7k Views Asked by At

Is there a way to close the selection menu of a DropdownButton containing all the DropdownMenuItems when an onTap function is executed (GestureDetector inside a DropdownMenuItem)?

Here is my implementation of the approach of Alperen Baskaya (in a slightly reduced version so that it is understandable). This approach however does not work yet and I am not sure whether it is because I have implemented it incorrectly or because the approach does not work for my problem.

class _BoatSelectionState extends State<BoatSelection> {
  FocusNode focusNode;
  
  @override
  void initState() {
    super.initState();
    focusNode = FocusNode();
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: 
            DropdownButtonHideUnderline(
              child: DropdownButton<Boat>(
                focusNode: focusNode,
                icon: Icon(
                  Icons.keyboard_arrow_down_rounded,
                  color: Colors.black,
                ),
                isExpanded: true,
                value: selectedBoat,
                onChanged: (Boat _boat) => Provider.of<BoatStreamsCubit>(context, listen: false).setBoat(_boat),
                selectedItemBuilder: (BuildContext context) {
                  return widget.boats.map<Widget>((Boat boat) {
                    return Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        BoatClassLogo(boat: boat),
                        Expanded(
                          child: Padding(
                            padding: const EdgeInsets.only(left: DesignValues.paddingMd),
                            child: BoatInformation(boat: boat),
                          ),
                        ),
                      ],
                    );
                  }).toList();
                },
                items: widget.boats.map<DropdownMenuItem<Boat>>((Boat _boat) {
                  return DropdownMenuItem<Boat>(
                    value: _boat,
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        Padding(
                          padding: const EdgeInsets.only(right: DesignValues.paddingMd),
                          child: BoatClassLogo(boat: _boat),
                        ),
                        Expanded(
                          child: BoatInformation(boat: _boat),
                        ),
                        GestureDetector(
                          onTap: () {
                            focusNode.unfocus();
                            Navigator.push(context, MaterialPageRoute(builder: (context) => BoatForm(CreationState.edit, _boat)));
                          },
                          child: Padding(
                            padding: const EdgeInsets.symmetric(horizontal: 5.0),
                            child: Icon(
                              Icons.edit,
                              color: AppColors.primary,
                            ),
                          ),
                        ),
                      ],
                    ),
                  );
                }).toList(),
              ),
          ),
        ),
      ],
    );
  }
}
2

There are 2 best solutions below

1
On BEST ANSWER

I looked up the internal implementation of DropdownMenu in dart.

The popover for DropdownMenu is created by using Navigator.push(). It waits for the user to click an item and returns the value with Navigator.pop(). So we can pop the popover manually by getting the dropdown's context via a GlobalKey.

late GlobalKey dropdownKey;  

@override
void initState() {
    super.initState();
    dropdownKey = GlobalKey();
}

...

DropdownButton<Boat>(
    key: dropdownKey,
    ...)

And remove it using Navigator.pop()

GestureDetector(
    onTap: () {
        Navigator.pop(dropdownKey.currentContext);

Full code:

class _BoatSelectionState extends State<BoatSelection> {
  GlobalKey dropdownKey;
  
  @override
  void initState() {
    super.initState();
    dropdownKey = GlobalKey(); // Init GlobalKey, allows to close the DropdownButton
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: 
            DropdownButtonHideUnderline(
              child: DropdownButton<Boat>(
                key: dropdownKey,
                icon: Icon(
                  Icons.keyboard_arrow_down_rounded,
                  color: Colors.black,
                ),
                isExpanded: true,
                value: selectedBoat,
                onChanged: (Boat _boat) => Provider.of<BoatStreamsCubit>(context, listen: false).setBoat(_boat),
                selectedItemBuilder: (BuildContext context) {
                  return widget.boats.map<Widget>((Boat boat) {
                    return Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        BoatClassLogo(boat: boat),
                        Expanded(
                          child: Padding(
                            padding: const EdgeInsets.only(left: DesignValues.paddingMd),
                            child: BoatInformation(boat: boat),
                          ),
                        ),
                      ],
                    );
                  }).toList();
                },
                items: widget.boats.map<DropdownMenuItem<Boat>>((Boat _boat) {
                  return DropdownMenuItem<Boat>(
                    value: _boat,
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: [
                        Padding(
                          padding: const EdgeInsets.only(right: DesignValues.paddingMd),
                          child: BoatClassLogo(boat: _boat),
                        ),
                        Expanded(
                          child: BoatInformation(boat: _boat),
                        ),
                        GestureDetector(
                          onTap: () {
                            Navigator.pop(dropdownKey.currentContext); // Closes the dropdown
                            Navigator.push(context, MaterialPageRoute(builder: (context) => BoatForm(CreationState.edit, _boat)));
                          },
                          child: Padding(
                            padding: const EdgeInsets.symmetric(horizontal: 5.0),
                            child: Icon(
                              Icons.edit,
                              color: AppColors.primary,
                            ),
                          ),
                        ),
                      ],
                    ),
                  );
                }).toList(),
              ),
          ),
        ),
      ],
    );
  }
}
4
On

If I got you right you can use focus node for dropdown menu.

FocusNode dropdown;

Initializing in initstate is needed;

dropdown = FocusNode();

child: DropdownButtonHideUnderline(
                         child: DropdownButton <String>(
                            focusNode: dropdown,

Then when you may think to close this menu execute in ontap;

  dropdown.unfocus();