How to open DropdownButton when other widget is tapped, in Flutter?

The other answer is the best way to do this, but as requested by the OP in comments, here are two very “hacky” ways to achieve this, yet without implementing custom widgets.

1. Access DropdownButton widget tree directly using GlobalKey

If we look at the source code of DropdownButton, we can notice that it uses GestureDetector to handle taps. However, it’s not a direct descendant of DropdownButton, and we cannot depend on tree structure of other widgets, so the only reasonably stable way to find the detector is to do the search recursively.

One example is worth a thousand explanations:

class DemoDropdown extends StatefulWidget {  
  @override
  InputDropdownState createState() => DemoDropdownState();
}

class DemoDropdownState<T> extends State<DemoDropdown> {
  /// This is the global key, which will be used to traverse [DropdownButton]s widget tree
  GlobalKey _dropdownButtonKey;

  void openDropdown() {
    GestureDetector detector;
    void searchForGestureDetector(BuildContext element) {
      element.visitChildElements((element) {
        if (element.widget != null && element.widget is GestureDetector) {
          detector = element.widget;
          return false;

        } else {
          searchForGestureDetector(element);
        }

        return true;
      });
    }

    searchForGestureDetector(_dropdownButtonKey.currentContext);
    assert(detector != null);

    detector.onTap();
  }

  @override
  Widget build(BuildContext context) {
    final dropdown = DropdownButton<int>(
      key: _dropdownButtonKey,
      items: [
        DropdownMenuItem(value: 1, child: Text('1')),
        DropdownMenuItem(value: 2, child: Text('2')),
        DropdownMenuItem(value: 3, child: Text('3')),
      ],
      onChanged: (int value) {},
    );

    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        Offstage(child: dropdown),
        FlatButton(onPressed: openDropdown, child: Text('CLICK ME')),
      ],
    );
  }
}

2. Use Actions.invoke

One of the recent features of Flutter is Actions (I’m not sure what it’s meant for, I’ve only noticed it today after flutter upgrade), and DropdownButton uses it for reacting to different… well, actions.

So a little tiny bit less hacky way to trigger the button would be to find the context of Actions widget and invoke the necessary action.

There are two advantages of this approach: firstly, Actions widget is a bit higher in the tree, so traversing that tree wouldn’t be as long as with GestureDetector, and secondly, Actions seems to be a more generic mechanism than gesture detection, so it’s less likely to disappear from DropdownButton in the future.

// The rest of the code is the same
void openDropdown() {
  _dropdownButtonKey.currentContext.visitChildElements((element) {
    if (element.widget != null && element.widget is Semantics) {
      element.visitChildElements((element) {
        if (element.widget != null && element.widget is Actions) {
          element.visitChildElements((element) {
            Actions.invoke(element, Intent(ActivateAction.key));
            return false;
          });
        }
      });
    }
  });
}

Leave a Comment