Create a side menu or Drawer in flutter for navigation in our app
Content Index
- What is a side menu or Drawer and what is it for?
- How to create a side menu?
- Creating the Drawer
- Items para el menú lateral
- Map the list of options objects to a list of widgets
- Create the Side Menu: Drawer in Flutter
- Header or header of the side menu
- Tap event and navigation
- Creating a Custom Scaffold and Varying the Drawer (or Static) and Responsiveness (OrientationBuilder)
- ️ How to Create a CustomScaffold
- The Problem of Copying and Pasting
- The Solution: CustomScaffold
- Advantages of the CustomScaffold
- Adaptive Design with OrientationBuilder
- Using OrientationBuilder
- Create Custom Scaffold
- 1. Small Screens (Mobile / Tablet)
- 2. Large Screens (Desktop / Web)
The navigation drawer usually opens from the left side of the screen, but you can also set it to open from the right side and take up 70 percent of the screen, and to close it, you can simply swipe or click out of the drawer.
We agreed in the previous post that we learned how to use the Dismissible widget type in Flutter.
What is a side menu or Drawer and what is it for?
A side menu is a UI element that is displayed on the side of an app and that displays navigation options or secondary actions. This type of UI element is widely used in the context of apps in Flutter or Material Design and Android. in general. It can be opened by swiping or touching a button or icon, and it scrolls through the main content of the app to reveal additional options. Users can interact with menu options to access different views or actions; It is widely used for navigation between different pages.
A side menu or Navigation Drawer is simply a navigation panel that is displayed from the left or right side, generally by pressing a hamburger-type button from the AppBar or Toolbar and that of course we can implement in our application in Flutter.
The objective of this menu is to present a set of options to our user that consists of functionalities that the application allows.
How to create a side menu?
Using Dart specifically the Flutter framework we can easily create it in our applications using a widget that allows us to create said component and we can easily bind it to a Scaffold through a property called drawer.
Creating the Drawer
Here you have to start with the typical, create a clean project in Flutter with Android Studio or Visual Studio Code, remove the initial code and create a Scaffold:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter NavBar',
),
home: Scaffold(
appBar: AppBar(title: Text("Menu Lateral"),),
drawer: CustomDrawer.getDrawer(),
)
);
}
}We are going to know how we can create a Drawer or side menu in Flutter just like we did in Android in a previous entry in which we created a side menu in Android; we are going to give the entire implementation of the code and then we will analyze the code in detail:
Remember that a side menu is just a vertical panel in which we can place a collection of elements that in our case would be a collection of items one stacked below another; therefore the Column is perfect for us here to stack a multitude of widgets for our header and options:
class DrawerItem {
String title;
IconData icon;
DrawerItem(this.title, this.icon);
}
class CustomDrawer {
static int selectedDrawerIndex = 1;
static final _drawerItems = [
DrawerItem("Perfil", Icons.person),
DrawerItem("Ver eventos", Icons.access_alarm),
DrawerItem("Crear eventos", Icons.add_alarm),
DrawerItem("Ver consejos", Icons.web),
DrawerItem("Aviso legal", Icons.info)
];
static _onTapDrawer(int itemPos, BuildContext context){
Navigator.pop(context); // cerramos el drawer
selectedDrawerIndex = itemPos;
}
static Widget getDrawer(BuildContext context) {
final prefs = new UserPreferences();
List<Widget> drawerOptions = [];
// armamos los items del menu
for (var i = 0; i < _drawerItems.length; i++) {
var d = _drawerItems[i];
drawerOptions.add(new ListTile(
leading: new Icon(d.icon),
title: new Text(d.title),
selected: i == selectedDrawerIndex,
onTap: () => _onTapDrawer(i, context),
));
}
// menu lateral
return Drawer(
child: Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text(prefs.name), accountEmail: Text(prefs.email)),
Column(children: drawerOptions)
],
),
);
}
}In general terms, before going into detail about all the code, the app was created to be consumed using static methods; that is, this is a separate class that you can consume from your Flutter pages through the getDrawer() method, just as we do in our course:
Items para el menú lateral
To facilitate the process of creating the items or the options of the side menu and to have everything better organized, we create an auxiliary class called _DrawerItem with the elements of which our menu item will consist; for our example, it has the usual, a title or name for the item and an associated icon, but you could customize it further and add other elements such as color; as you can see, this class is private, the reason is that we are only interested in using it in the class that is in charge of defining the vertical panel or side menu.
And from the code above you can also see that we have an array with the menu items that use the class that we defined and explained above.
But we cannot process an array of classes as such, remember that everything in Flutter is a widget and a side menu is no exception, so you could bring this list that we have defined as base or hardcode in our build function, for example from a Rest Api.
And we pass this list to a List of widgets of type ListTile that we will use later.
Map the list of options objects to a list of widgets
The next thing we have in the list is to map our list, that is, we have a list of objects, but drawerItems but we need is a list of widgets; there are many ways in which we can do this, but the most interesting is to use the map method that allows us to map from one type of data to another; as you can see in the implementation, we change from a list of objects to a list of widgets; the interesting point here is that the map function implements an anonymous function in which we can do any kind of operation to convert our initial item to another type of object, which in this case would be a widget that in the end will be our list of widgets. ; since the map function can be assigned to another object and then we call the toList() method to go from a map to a list.
Create the Side Menu: Drawer in Flutter
Header or header of the side menu
Now, with the basic data defined and ready to use, we create our side menu, for that we make use of the Scaffold as the root element of our app and its property called drawer in which we can pass a widget that will be our Drawer side menu; This side menu receives a child as usual in this type of component and then we define a Column, because we are going to define a list of elements or widgets; The first thing we will place will be a UserAccountsDrawerHeader, which is a widget to define a header, as easy as the famous header to define elements such as the profile image, username or account, email, among other aspects that you can review.
Tap event and navigation
After the Drawer header, we pass our list of options, it's that simple, we have everything we did in the previous block in a function that we pass from one to our side menu and that's it; As you can see, each menu item can implement a tap event which we have defined by passing the index of the tapped item; and from here comes the ListTile widget that allows us to define texts, icons and the tap event.
Another optional widget in this implementation would be the ConstrainedBox that allows us to define a box with maximum or minimum dimensions, it is a kind of responsive container with extremes; You can look for more information about it in the official documentation.
Another important point that we have not yet seen and that we are going to discuss in another entry is navigation between screens, which is a fundamental issue and without it, the options on our side menu will be of little use.
But with this we have our side menu implemented and ready to do whatever you want to do.
Creating a Custom Scaffold and Varying the Drawer (or Static) and Responsiveness (OrientationBuilder)
I want to talk to you about two main concepts I implemented in my Academy application:
- The advantage of creating a Custom Scaffold (CustomScaffold).
- How to create an adaptive side menu (Drawer) that changes its behavior based on the screen size.
Notice that if we reduce the screen, the menu switches to the classic mobile approach (a collapsible Drawer). If the screen grows (the approach for web, tablet, Mac, or Windows), the menu becomes static on the side. This allows us to take advantage of the available space to place more elements, and I'll show you how I did it.
️ How to Create a CustomScaffold
In Flutter, the top widget is the MaterialApp. To create a page, we use the Scaffold. Therefore, each option and section of your application will have its own Scaffold.
The Problem of Copying and Pasting
Normally, if we want to customize the Scaffold (such as adding a Drawer), we would have to copy and paste that configuration into every single page of the application.
The problem is obvious: if you decide to change something in the Scaffold (for example, the design of the AppBar or the behavior of the Drawer), you would have to modify all pages individually.
The Solution: CustomScaffold
For this reason, I decided to create a widget called CustomScaffold. This is the widget that we ultimately use on every page.
The CustomScaffold is essentially a StatefulWidget that receives the essential elements of the Scaffold, like the title and the body, and optional widgets like endDrawer or floatingActionButton.
class CustomScaffold extends StatefulWidget {
final String title;
final Widget body;
// ... otros parámetros como endDrawer, floatingActionButton
// ...
}Advantages of the CustomScaffold
The main benefit lies in centralization. If, for example, I wanted to implement the logic for the floating action button (like the one I use for filtering on the posts page), I only had to modify this central file.
Another example of its usefulness: in the initState method of this custom widget, I can add an additional process, such as checking the internet connection (checkisInternetConnection). This change is automatically applied to all pages that use the CustomScaffold. This is the beauty of modularization.
Adaptive Design with OrientationBuilder
Now, let's see how we achieve the adaptive behavior of the side menu: fixed on large screens and collapsible (Drawer) on small screens.
In Flutter, everything is a widget, so we can easily manipulate the arrangement of elements. Inside our CustomScaffold, we use the OrientationBuilder widget.
Using OrientationBuilder
The OrientationBuilder is a listener that executes every time the device or window changes size or orientation. Inside its constructor (builder), we can apply conditional logic:
return OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
if (getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.sm ||
getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.md) {
// ... Lógica para pantallas pequeñas (Móviles / Tablets)
// ...
} else {
// window lg - Lógica para pantallas grandes (Web / Escritorio)
// ...
}
});Complete example:
OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
if (getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.sm ||
getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.md) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: DrawerComponent(true),
***
} else {
//window lg
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
***
body: Row(
children: <Widget>[
SizedBox(
width: 300,
height: double.infinity,
child: DrawerComponent(),
),
Container(
width: 1,
height: double.infinity,
color: Colors.grey,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(child: widget.body),
))
],Create Custom Scaffold
We create a widget with the custom scaffold that we will use later; you can customize any aspect with properties and base implementation:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:desarrollolibre/generated/locale_keys.g.dart';
import 'package:desarrollolibre/components/drawer_component.dart';
import 'package:desarrollolibre/provider/app_model.dart';
import 'package:desarrollolibre/utils/windows_sizes.dart';
import 'package:desarrollolibre/utils/academy_helper.dart';
import 'package:desarrollolibre/utils/helpers.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
class CustomScaffold extends StatefulWidget {
final String title;
final Widget body;
final Widget? endDrawer;
final FloatingActionButton? floatingActionButton;
const CustomScaffold(this.title, this.body,
[this.endDrawer, this.floatingActionButton]);
@override
State<CustomScaffold> createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> {
late AppModel appModel;
bool thereIsInternet = true;
@override
void initState() {
appModel = Provider.of<AppModel>(context, listen: false);
if (!appModel.userCredentialsCheck) {
checkuserVerified(context);
}
init();
super.initState();
}
Future<bool> checkisInternetConnection() async {
var connectivityResult = await (Connectivity().checkConnectivity());
return ConnectivityResult.none != connectivityResult;
}
init() async {
thereIsInternet = await checkisInternetConnection();
if (!thereIsInternet) {
// no hay internet
setState(() {});
}
}
@override
Widget build(BuildContext context) {
if (!thereIsInternet) {
// no hay conexion
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Text('No hay Internet'),
);
}
return OrientationBuilder(
builder: (BuildContext context, Orientation orientation) {
if (getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.sm ||
getBreakpoint(MediaQuery.of(context).size.width) ==
WindowsBreakpoint.md) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
drawer: DrawerComponent(true),
endDrawer: widget.endDrawer,
floatingActionButton:
appModel.userIsLogging && !appModel.userVerified
? FloatingActionButton.extended(
onPressed: () {
AcademyHelper.verifyUserPost(appModel.userToken);
showToastMessage(context,
"${LocaleKeys.sentEmailTo.tr()} ${appModel.userEmail} ${LocaleKeys.verifyAccount.tr()}");
},
label: Text(LocaleKeys.verifyUser.tr()),
)
: widget.floatingActionButton,
body: SafeArea(child: widget.body));
} else {
//window lg
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
floatingActionButton: appModel.userIsLogging && !appModel.userVerified
? FloatingActionButton.extended(
onPressed: () {
AcademyHelper.verifyUserPost(appModel.userToken);
showToastMessage(context,
"${LocaleKeys.sentEmailTo.tr()} ${appModel.userEmail} ${LocaleKeys.verifyAccount.tr()}");
},
label: Text(LocaleKeys.verifyUser.tr()),
)
: widget.floatingActionButton,
body: Row(
children: <Widget>[
SizedBox(
width: 300,
height: double.infinity,
child: DrawerComponent(),
),
Container(
width: 1,
height: double.infinity,
color: Colors.grey,
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SafeArea(child: widget.body),
))
],
),
endDrawer: widget.endDrawer,
);
}
});
}
}1. Small Screens (Mobile / Tablet)
If the breakpoint detects that the screen is small (sm or md):
- We return a classic Scaffold.
- We use the native drawer: property of the Scaffold and pass our DrawerComponent widget to it. This creates the collapsible menu that opens from the AppBar.
2. Large Screens (Desktop / Web)
If the breakpoint indicates that the screen is large (lg):
- We return a Scaffold without the drawer property.
- The body of the Scaffold is built using a Row widget.
- Inside the Row, we place our side menu (DrawerComponent) with a fixed width (e.g., SizedBox(width: 300, ...)), creating the static menu.
- The rest of the page content (widget.body) is wrapped in an Expanded widget so that it occupies the remaining space.
This is the key to the trick: by using conditional logic inside a widget that reacts to size changes, we achieve a completely adaptive design; the menu looks like this:
return Drawer(
child: SafeArea(
child: Column(
children: <Widget>[
const SizedBox(
height: 15,
),
MediaQuery.of(context).size.height > 700
? SizedBox(
width: double.infinity,
height: 120,
child: CircleAvatar(
backgroundColor: Colors.purple,
child: Text(
appModel.userIsLogging
? appModel.userEmail.substring(0, 1).toUpperCase() +
appModel.userEmail.substring(1, 2)
: 'DL',
style:
const TextStyle(fontSize: 40, color: Colors.white),
),
),
)
: const SizedBox(),
const SizedBox(
height: 15,
),
Text('${LocaleKeys.hello.tr()} ${appModel.userEmail}'),
Expanded(child: _Items(activatePopInBack)),
ListTile(
leading: const Icon(Icons.circle, color: Colors.purple),
title: const Text('Dark Mode'),
trailing: Switch.adaptive(
value: userPreference.themeDarkMode,
activeColor: Colors.purple,
onChanged: (value) {
userPreference.themeDarkMode = value;
appTheme.darkTheme = value;
AcademyHelper.userExtraPost(appModel.userToken, value);
}),
),
],
),
),
);
}
}Siguiente paso, crea una tabla con el widget DataTable en Flutter.
I agree to receive announcements of interest about this Blog.
We are going to know how we can create a side menu or Drawer with options, headers and user information in Flutter.