DEV Community

Nash
Nash

Posted on

8 1

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"?


 dart
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.


 dart
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


 dart
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


 dart
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.

Image of Timescale

Timescale – the developer's data platform for modern apps, built on PostgreSQL

Timescale Cloud is PostgreSQL optimized for speed, scale, and performance. Over 3 million IoT, AI, crypto, and dev tool apps are powered by Timescale. Try it free today! No credit card required.

Try free

Top comments (0)

Sentry mobile image

Mobile Vitals: A first step to Faster Apps

Slow startup times, UI hangs, and frozen frames frustrate users—but they’re also fixable. Mobile Vitals help you measure and understand these performance issues so you can optimize your app’s speed and responsiveness. Learn how to use them to reduce friction and improve user experience.

Read the guide