DEV Community

Nash
Nash

Posted on

Adding Expandable Side Bar using NavigationRail in Flutter.

If this is what you want to achieve, then this post is for you.

Flutter Web With Expandable Sidebar

Normally, for the mobile app, using the BottomNavigationBar would be fine. However, BottomNavigationBar for a desktop website? No one do that!

So here is the alternative.
Introducing NavigationRail, this is an ideal widget for implementing sidebar on the web/desktop application.

1. Where to Put?

Well, since it's a sidebar, therefore this widget will be insert in the either left or right side of your page, and in order to do that, we will use Row widget.

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

  @override
  State<PageWithSideBar> createState() => _PageWithSideBarState();
}

class _PageWithSideBarState extends State<PageWithSideBar> {
  int _selectedIndex = 0;
  final List<String> _destinations = ['Home', 'Calendar', 'Email'];

  Widget _buildNavigationRail() {
    return Container() //TODO: We are going to implement here
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: <Widget>[
          _buildNavigationRail(), // HERE: This is our SideBar
          Expanded(
            child: Center(
              child: Text('This is ${_destinations[_selectedIndex]} Page'),
            ),
          )
        ],
      ),
    );
  }
}


Enter fullscreen mode Exit fullscreen mode

Notice that we use Expanded widget in our main content so it will take all the space as much as possible.

2. Now let's implement NavigationRail.

Widget _buildNavigationRail() {
    return NavigationRail(
      labelType: NavigationRailLabelType.selected,
      selectedIndex: _selectedIndex,
      onDestinationSelected: (int index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      destinations: const <NavigationRailDestination>[
        NavigationRailDestination(
          icon: Icon(Icons.home_outlined),
          selectedIcon: Icon(Icons.home),
          label: Text('Home'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.calendar_month_outlined),
          selectedIcon: Icon(Icons.calendar_month),
          label: Text('Calendar'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.email_outlined),
          selectedIcon: Icon(Icons.email),
          label: Text('Email'),
        ),
      ],
    );
  }

Enter fullscreen mode Exit fullscreen mode

Let's see how it turned out

Flutter Web With Sidebar

In my opinion, this sidebar still look like an mobile one, so what about make it feel more like a website's sidebar?

We are going to do that by make it become Expandable side bar. Good news is that the Flutter Dev know exactly what we want, as there is an property call extended, so why don't we set its value to "true"?

NavigationRail(
extended: true, 
labelType: NavigationRailLabelType.selected, ...)

Enter fullscreen mode Exit fullscreen mode

Flutter Sidebar Error

Whoa, There is error here. It's mention that if we set the "extended" to true, then we have to set the labelType to "none"
Okay understandable, since having both label at the bottom of the icon and next to the icon would be really weird.

NavigationRail(
extended: true,   
labelType: NavigationRailLabelType.none, ...)
Enter fullscreen mode Exit fullscreen mode

Now the error is gone

Flutter Web With Expandable Sidebar

But hey, seems like something is missing here... It should be togglable, so we should have something like menu icon.

Okay, here is my solution,

  1. Declare new boolean variable call _isExtended as a property of NavigationRail
  2. Add AppBar with the IconButton to toggle the state of _isExtended

Here is the code

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

  @override
  State<PageWithSideBar> createState() => _PageWithSideBarState();
}

class _PageWithSideBarState extends State<PageWithSideBar> {
  int _selectedIndex = 0;
  final List<String> _destinations = ['Home', 'Calendar', 'Email'];
  bool _isExpanded = false; //NEW VARIABLE
  Widget _buildNavigationRail() {
    return NavigationRail(
      extended: _isExpanded, //NEW VARIABLE
      labelType: NavigationRailLabelType.none,
      selectedIndex: _selectedIndex,
      onDestinationSelected: (int index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      destinations: const <NavigationRailDestination>[
        NavigationRailDestination(
          icon: Icon(Icons.home_outlined),
          selectedIcon: Icon(Icons.home),
          label: Text('Home'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.calendar_month_outlined),
          selectedIcon: Icon(Icons.calendar_month),
          label: Text('Calendar'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.email_outlined),
          selectedIcon: Icon(Icons.email),
          label: Text('Email'),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //ADDED APP BAR
        title: const Text('Navigation Rails Example'),
        leading: IconButton( 
          icon: const Icon(Icons.menu),
          onPressed: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
        ),
      ),
      body: Row(
        children: <Widget>[
          _buildNavigationRail(),
          Expanded(
            child: Center(
              child: Text('This is ${_destinations[_selectedIndex]} Page'),
            ),
          )
        ],
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

Here is the results
Flutter Web With Expandable Sidebar

Here is the full code

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  static const String _title = 'Navigation Rails Example';

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: _title,
      home: PageWithSideBar(),
    );
  }
}

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

  @override
  State<PageWithSideBar> createState() => _PageWithSideBarState();
}

class _PageWithSideBarState extends State<PageWithSideBar> {
  int _selectedIndex = 0;
  final List<String> _destinations = ['Home', 'Calendar', 'Email'];
  bool _isExpanded = false; 
  Widget _buildNavigationRail() {
    return NavigationRail(
      extended: _isExpanded, 
      labelType: NavigationRailLabelType.none,
      selectedIndex: _selectedIndex,
      onDestinationSelected: (int index) {
        setState(() {
          _selectedIndex = index;
        });
      },
      destinations: const <NavigationRailDestination>[
        NavigationRailDestination(
          icon: Icon(Icons.home_outlined),
          selectedIcon: Icon(Icons.home),
          label: Text('Home'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.calendar_month_outlined),
          selectedIcon: Icon(Icons.calendar_month),
          label: Text('Calendar'),
        ),
        NavigationRailDestination(
          icon: Icon(Icons.email_outlined),
          selectedIcon: Icon(Icons.email),
          label: Text('Email'),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar( //ADDED APP BAR
        title: const Text('Navigation Rails Example'),
        leading: IconButton( 
          icon: const Icon(Icons.menu),
          onPressed: () {
            setState(() {
              _isExpanded = !_isExpanded;
            });
          },
        ),
      ),
      body: Row(
        children: <Widget>[
          _buildNavigationRail(),
          Expanded(
            child: Center(
              child: Text('This is ${_destinations[_selectedIndex]} Page'),
            ),
          )
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Ok we are good to go now.

Thank you for reading.

Top comments (0)