Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter Web ShowMenu Does Not Move With Screen Resize

Tricky question, hoping someone has a nice thought.

I call showMenu(...) on my text button in flutter, which works well. However, sometimes during screen resizing, the menu gets stuck somewhere on the screen (away from its intended position). Sometimes it follows its anchored position. Very odd, and I noticed this behavior with the dropdown menu too.

Here is my sample code. I want to either move the menu always with the screen, or in worst case, hide the menu on a screen resizing event. Any thoughts on how to do either would be great!

class ZHomeMenuBar extends StatefulWidget {
  const ZHomeMenuBar({Key? key}) : super(key: key);

  @override
  State<ZHomeMenuBar> createState() => _ZHomeMenuBarState();
}

class _ZHomeMenuBarState extends State<ZHomeMenuBar> {
  final GlobalKey _mesKey = GlobalKey();
  final GlobalKey _accKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    final zuser = Provider.of<ZUser?>(context);
    return Container(
      height: 66,
      decoration: BoxDecoration(color: context.backgroundColor),
      padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: Row(
        children: [
          Text(zuser == null ? "" : zuser.displayName ?? ""),
          const Spacer(),
          ZTextButton(text: "Portfolio", onPressed: () => {}),
          context.sh,
          ZTextButton(
              key: _mesKey,
              text: "Messages",
              onPressed: () {
                _showMessage(context);
              }),
          context.sh,
          ZTextButton(key: _accKey, text: "Account", onPressed: () {}),
          context.sh,
        ],
      ),
    );
  }

  _showMessage(context) {
    final RenderBox renderBox =
        _mesKey.currentContext?.findRenderObject() as RenderBox;
    final Size size = renderBox.size;
    final Offset offset = renderBox.localToGlobal(Offset.zero);

    showMenu(
        context: context,
        position: RelativeRect.fromLTRB(offset.dx, offset.dy + size.height,
            offset.dx + size.width, offset.dy + size.height),
        items: [
          PopupMenuItem<String>(child: const Text('menu option 1'), value: '1'),
          PopupMenuItem<String>(child: const Text('menu option 2'), value: '2'),
          PopupMenuItem<String>(child: const Text('menu option 3'), value: '3'),
        ]);
  }
}
like image 528
AlexK Avatar asked Dec 05 '21 17:12

AlexK


1 Answers

In order to hide the menu when you're resizing the screen, you can use dart:html's window object to add an event listener to the browser's resize and pop the context of the menu.

Here is the updated code for your ZHomeMenuBar widget:

class ZHomeMenuBar extends StatefulWidget {
  ...
}

class _ZHomeMenuBarState extends State<ZHomeMenuBar> {
  ...

  Timer? timer;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    if (kIsWeb) {

      final int debounceDuration = 100;

      html.window.addEventListener("resize", (_) {
        final modalRoute = ModalRoute.of(context);

        if (modalRoute == null) {
          return;
        }

        if (!modalRoute.isCurrent) {
          Navigator.pop(context);

          if (timer != null && timer!.isActive) {
            timer!.cancel();
          }

          timer = Timer(Duration(milliseconds: 100), () {
            _showMessage(context);
          });
        }
      });

    }

  }

  @override
  void dispose() {
    super.dispose();
    timer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    ...
  }

  _showMessage(context) {
    ...
  }
}

So what the code in the didChangeDependencies method does is:

  1. The window listens to the resize event.
  2. The ModalRoute of the context is obtained.
  3. The method returns if the ModalRoute is null
  4. The isCurrent property of the ModalRoute is checked. If it is false, it means the ModalRoute isn't the top-most route on the navigator and that means there is a dialog on the screen.
  5. So if isCurrent is false, we pop the context (this removes the dialog), and set a short timer with the debounceDuration and then show the dialog. This uses the newly recalculated dimensions and shows the dialog at the right position.
  6. If the timer is active while another timer is running, we cancel the previous timer and assign the new one to the timer variable.

You can update the debounceDuration as you need.

like image 174
Victor Eronmosele Avatar answered Nov 17 '22 20:11

Victor Eronmosele