Making fixed app-wide menu instead of Drawer on tablets in Flutter

3,662

The trick was to use a nested navigator. If viewport's width is big, I put a menu in a row with content, otherwise pass a menu to Scaffold as drawer parameter.

portrait landscape

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage> {
  final routes = List.generate(20, (i) => 'test $i');

  final navigatorKey = GlobalKey<NavigatorState>();

  bool isMenuFixed(BuildContext context) {
    return MediaQuery.of(context).size.width > 500;
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    final menu = Container(
      color: theme.canvasColor,
      child: SafeArea(
        right: false,
        child: Drawer(
          elevation: 0,
          child: ListView(
            children: <Widget>[
              for (final s in routes)
                ListTile(
                  title: Text(s),
                  onTap: () {
                    // Using navigator key, because the widget is above nested navigator  
                    navigatorKey.currentState.pushNamedAndRemoveUntil(s, (r) => false);

                    // navigatorKey.currentState.pushNamed(s);
                  },
                ),
            ],
          ),
        )
      )
    );

    return Row(
      children: <Widget>[
        if (isMenuFixed(context))
          menu,
        Expanded(
          child: Navigator(
            key: navigatorKey,
            initialRoute: '/',
            onGenerateRoute: (settings) {
              return MaterialPageRoute(
                builder: (context) {
                  return Scaffold(
                    appBar: AppBar(
                      title: Text(settings.name),
                    ),
                    body: SafeArea(
                      child: Text(settings.name),
                    ),
                    drawer: isMenuFixed(context) ? null : menu,
                  );
                },
                settings: settings
              );
            },
          ),
        ),
      ],
    );
  }
}
Share:
3,662
Igor Kharakhordin
Author by

Igor Kharakhordin

Updated on December 15, 2022

Comments

  • Igor Kharakhordin
    Igor Kharakhordin over 1 year

    My application has a lot of routes and almost every route uses Scaffold with the same Drawer menu to navigate inside the app (my own CustomDrawer widget). As for devices with big screen, I want to always show the menu on the left side in layout, instead of using Drawer (it works like this in Gmail app. I attached a pic). In other words, I need to make a responsive layout with fixed menu.

    example

    What I've tried:

    • I know that you can use LayoutBuilder to learn constraints size;
    • Making same layout inside every route will work, but it's bad solution because each route will build menu for itself (scroll position will be different, there will be many states for many menus etc.). I need one app-wide menu for many different routes, but it's impossible to make layout on top routes;
    • Squashing all routes into one route with changing state of main content will take a lot of refactoring and doesn't sound good at all.

    In React the app layout would look something like this:

        <App>
          <Menu />
          <main className="content">
            <Switch>
              <Route path="/about" component={About} />
              <Route path="/contact" component={Contact} />
              <Route path="/" component={Home} />
            </Switch>
          </main>
        </App>
    

    But I have no idea how to make something like this in Flutter.

    tl;dr: To make UI responsive for big screens, I want to show fixed menu instead of Drawer. One menu for whole app, not for every route.