Skip to content

[go_router] DropdownMenu behind NavigationBar #155034

@adonisRodxander

Description

@adonisRodxander

Steps to reproduce

  1. Run build runner
  2. Set the phone to landscape
  3. Open DropdownMenu

Expected results

The DropdownMenu should hover over the NavigationBar, or start from the NavigationBar up. It should behave similarly to when a DropdownMenu is near the AppBar, it never hovers over it.

Actual results

The Dropdown Menu appears behind the NavigationBar hiding some options.

Code sample (1) shell_route_example.dart

Code sample
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs, unreachable_from_main

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

part 'shell_route_example.g.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  App({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routerConfig: _router,
      );

  final GoRouter _router = GoRouter(
    routes: $appRoutes,
    initialLocation: '/notifications',
  );
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('Test')),
      );
}

@TypedShellRoute<MyShellRouteData>(
  routes: <TypedRoute<RouteData>>[
    TypedGoRoute<NotificationRouteData>(path: '/notifications'),
    TypedGoRoute<BarRouteData>(path: '/bar'),
  ],
)
class MyShellRouteData extends ShellRouteData {
  const MyShellRouteData();

  @override
  Widget builder(
    BuildContext context,
    GoRouterState state,
    Widget navigator,
  ) {
    return MyShellRouteScreen(child: navigator);
  }
}

class NotificationRouteData extends GoRouteData {
  const NotificationRouteData();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const NotificationScreen();
  }
}

class BarRouteData extends GoRouteData {
  const BarRouteData();

  @override
  Widget build(BuildContext context, GoRouterState state) {
    return const BarScreen();
  }
}

class MyShellRouteScreen extends StatelessWidget {
  const MyShellRouteScreen({required this.child, super.key});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      body: child,
      appBar: AppBar(title: const Text('Test')),
      bottomNavigationBar: NavigationBar(
        backgroundColor: Colors.blue.withOpacity(0.5),
        labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
        onDestinationSelected: (int index) {
          if (index == 0) {
            context.go('/notifications');
          } else if (index == 1) {
            context.go('/bar');
          }
        },
        indicatorColor: Colors.amber,
        destinations: const <Widget>[
          NavigationDestination(
            selectedIcon: Icon(Icons.home),
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Badge(child: Icon(Icons.notifications_sharp)),
            label: 'Notifications',
          ),
        ],
      ),
    );
  }
}

class NotificationScreen extends StatelessWidget {
  const NotificationScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Card(
      shadowColor: Colors.transparent,
      margin: EdgeInsets.all(8.0),
      child: SizedBox.expand(
        child: Center(
          child: Text('Home page'),
        ),
      ),
    );
  }
}

class BarScreen extends StatelessWidget {
  const BarScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 20,
      itemBuilder: (BuildContext context, int index) {
        if (index == 10) {
          return DropdownMenu(
            menuStyle: const MenuStyle(
              visualDensity: VisualDensity.standard,
              alignment: AlignmentDirectional.topStart,
            ),
            requestFocusOnTap: false,
            expandedInsets: EdgeInsets.zero,
            dropdownMenuEntries: List.generate(
              4,
              (index) {
                return DropdownMenuEntry(
                  label: 'Entry $index',
                  value: 'Entry $index',
                );
              },
            ),
          );
        } else {
          return Text(
            'Hello $index',
          );
        }
      },
    );
  }
}

Code sample (1.1) shell_route_example.g.dart

Code sample
// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'shell_route_example.dart';

// **************************************************************************
// GoRouterGenerator
// **************************************************************************

List<RouteBase> get $appRoutes => [
      $myShellRouteData,
    ];

RouteBase get $myShellRouteData => ShellRouteData.$route(
      factory: $MyShellRouteDataExtension._fromState,
      routes: [
        GoRouteData.$route(
          path: '/notifications',
          factory: $FooRouteDataExtension._fromState,
        ),
        GoRouteData.$route(
          path: '/bar',
          factory: $BarRouteDataExtension._fromState,
        ),
      ],
    );

extension $MyShellRouteDataExtension on MyShellRouteData {
  static MyShellRouteData _fromState(GoRouterState state) =>
      const MyShellRouteData();
}

extension $FooRouteDataExtension on NotificationRouteData {
  static NotificationRouteData _fromState(GoRouterState state) =>
      const NotificationRouteData();

  String get location => GoRouteData.$location(
        '/notifications',
      );

  void go(BuildContext context) => context.go(location);

  Future<T?> push<T>(BuildContext context) => context.push<T>(location);

  void pushReplacement(BuildContext context) =>
      context.pushReplacement(location);

  void replace(BuildContext context) => context.replace(location);
}

extension $BarRouteDataExtension on BarRouteData {
  static BarRouteData _fromState(GoRouterState state) => const BarRouteData();

  String get location => GoRouteData.$location(
        '/bar',
      );

  void go(BuildContext context) => context.go(location);

  Future<T?> push<T>(BuildContext context) => context.push<T>(location);

  void pushReplacement(BuildContext context) =>
      context.pushReplacement(location);

  void replace(BuildContext context) => context.replace(location);
}

Screenshots or Video with simple go_router_builder

Screenshots / Video demonstration

image


UPDATE: The bug occurs with simple go_router.

Code sample

Code sample
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// ignore_for_file: public_member_api_docs, unreachable_from_main

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  App({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp.router(
        routerConfig: _router,
      );

  final GoRouter _router = GoRouter(
    initialLocation: '/notifications',
    routes: [
      ShellRoute(
        builder: (context, state, child) {
          return MyShellRouteScreen(child: child);
        },
        routes: [
          GoRoute(
            path: '/notifications',
            builder: (context, state) => const NotificationScreen(),
          ),
          GoRoute(
            path: '/bar',
            builder: (context, state) => const BarScreen(),
          ),
        ],
      ),
    ],
  );
}

class MyShellRouteScreen extends StatelessWidget {
  const MyShellRouteScreen({required this.child, super.key});

  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      body: child,
      appBar: AppBar(title: const Text('Test')),
      bottomNavigationBar: NavigationBar(
        backgroundColor: Colors.blue.withOpacity(0.5),
        labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
        onDestinationSelected: (int index) {
          if (index == 0) {
            context.go('/notifications');
          } else if (index == 1) {
            context.go('/bar');
          }
        },
        indicatorColor: Colors.amber,
        destinations: const <Widget>[
          NavigationDestination(
            selectedIcon: Icon(Icons.home),
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Badge(child: Icon(Icons.notifications_sharp)),
            label: 'Notifications',
          ),
        ],
      ),
    );
  }
}

class NotificationScreen extends StatelessWidget {
  const NotificationScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Card(
      shadowColor: Colors.transparent,
      margin: EdgeInsets.all(8.0),
      child: SizedBox.expand(
        child: Center(
          child: Text('Home page'),
        ),
      ),
    );
  }
}

class BarScreen extends StatelessWidget {
  const BarScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 20,
      itemBuilder: (BuildContext context, int index) {
        if (index == 10) {
          return DropdownMenu(
            menuStyle: const MenuStyle(
              visualDensity: VisualDensity.standard,
              alignment: AlignmentDirectional.topStart,
            ),
            requestFocusOnTap: false,
            expandedInsets: EdgeInsets.zero,
            dropdownMenuEntries: List.generate(
              4,
              (index) {
                return DropdownMenuEntry(
                  label: 'Entry $index',
                  value: 'Entry $index',
                );
              },
            ),
          );
        } else {
          return Text(
            'Hello $index',
          );
        }
      },
    );
  }
}

Screenshots or Video with simple go_router

Screenshots / Video demonstration

image


UPDATE: The bug does not occur if we use a shell without using go_router

Code sample

Code sample
import 'package:flutter/material.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyShellRouteScreen(),
    );
  }
}

class MyShellRouteScreen extends StatefulWidget {
  const MyShellRouteScreen({super.key});

  @override
  _MyShellRouteScreenState createState() => _MyShellRouteScreenState();
}

class _MyShellRouteScreenState extends State<MyShellRouteScreen> {
  int _selectedIndex = 0;

  final List<Widget> _pages = const [
    NotificationScreen(),
    BarScreen(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Test')),
      body: _pages[_selectedIndex],
      bottomNavigationBar: NavigationBar(
        backgroundColor: Colors.blue.withOpacity(0.5),
        labelBehavior: NavigationDestinationLabelBehavior.alwaysHide,
        selectedIndex: _selectedIndex,
        indicatorColor: Colors.amber,
        onDestinationSelected: _onItemTapped,
        destinations: const [
          NavigationDestination(
            selectedIcon: Icon(Icons.home),
            icon: Icon(Icons.home_outlined),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Badge(child: Icon(Icons.notifications_sharp)),
            label: 'Notifications',
          ),
        ],
      ),
    );
  }
}

class NotificationScreen extends StatelessWidget {
  const NotificationScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return const Card(
      shadowColor: Colors.transparent,
      margin: EdgeInsets.all(8.0),
      child: SizedBox.expand(
        child: Center(
          child: Text('Home page'),
        ),
      ),
    );
  }
}

class BarScreen extends StatelessWidget {
  const BarScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: 20,
      itemBuilder: (BuildContext context, int index) {
        if (index == 10) {
          return DropdownMenu<String>(
            menuStyle: const MenuStyle(
              visualDensity: VisualDensity.standard,
              alignment: AlignmentDirectional.topStart,
            ),
            requestFocusOnTap: false,
            expandedInsets: EdgeInsets.zero,
            dropdownMenuEntries: List.generate(4, (index) {
              return DropdownMenuEntry(
                label: 'Entry $index',
                value: 'Entry $index',
              );
            }),
            onSelected: (value) {
              // Handle selection
            },
          );
        } else {
          return ListTile(
            title: Text('Hello $index'),
          );
        }
      },
    );
  }
}

Screenshots or Video without go_router

Screenshots / Video demonstration

image

Logs

Logs
[Paste your logs here]

Flutter Doctor output

Doctor output
[✓] Flutter (Channel stable, 3.24.1, on Ubuntu 24.04.1 LTS 6.8.0-44-generic, locale en_US.UTF-8)
    • Flutter version 3.24.1 on channel stable at /home/user/fvm/versions/3.24.1
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 5874a72aa4 (3 weeks ago), 2024-08-20 16:46:00 -0500
    • Engine revision c9b9d5780d
    • Dart version 3.5.1
    • DevTools version 2.37.2

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /../.../ANDROID_SDK
    • Platform android-34, build-tools 34.0.0
    • Java binary at: /home/user/android-studio/jbr/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)
    • All Android licenses accepted.

[✓] Chrome - develop for the web
    • Chrome at google-chrome

[✗] Linux toolchain - develop for Linux desktop
    ✗ clang++ is required for Linux development.
      It is likely available from your distribution (e.g.: apt install clang), or can be downloaded from https://releases.llvm.org/
    ✗ CMake is required for Linux development.
      It is likely available from your distribution (e.g.: apt install cmake), or can be downloaded from https://cmake.org/download/
    ✗ ninja is required for Linux development.
      It is likely available from your distribution (e.g.: apt install ninja-build), or can be downloaded from https://github.com/ninja-build/ninja/releases
    ✗ pkg-config is required for Linux development.
      It is likely available from your distribution (e.g.: apt install pkg-config), or can be downloaded from
      https://www.freedesktop.org/wiki/Software/pkg-config/

[✓] Android Studio (version 2024.1)
    • Android Studio at /home/user/android-studio
    • Flutter plugin version 81.0.2
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • android-studio-dir = /home/user/android-studio
    • Java version OpenJDK Runtime Environment (build 17.0.11+0-17.0.11b1207.24-11852314)

[✓] VS Code (version 1.93.0)
    • VS Code at /usr/share/code
    • Flutter extension version 3.96.0

[✓] Connected device (3 available)
    • Android SDK built for x86 64 (mobile) • emulator-5554 • android-x64    • Android 6.0 (API 23) (emulator)
    • Linux (desktop)                       • linux         • linux-x64      • Ubuntu 24.04.1 LTS 6.8.0-44-generic
    • Chrome (web)                          • chrome        • web-javascript • Google Chrome 128.0.6613.137

[✓] Network resources
    • All expected network resources are available.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work lista: layoutSystemChrome and Framework's Layout Issuesf: material designflutter/packages/flutter/material repository.found in release: 3.24Found to occur in 3.24found in release: 3.26Found to occur in 3.26frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packagepackageflutter/packages repository. See also p: labels.team-designOwned by Design Languages teamtriaged-designTriaged by Design Languages team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions