Flutter : NavigationRail Widget- New in v1.17

Ankit Chowdhury
Flutter Community
Published in
7 min readMay 10, 2020

As a dev, who has active apps on App stores and hundreds of thousands of users who depend on it, it’s particularly difficult to keep track of every new framework/API being launched every day. We tend to be happy with the tools we have mastered already and only look to change it, when absolutely necessary.

But the experience has been a tad different with Flutter. The way Team Flutter has packaged and presented these tools(widgets in this case), I have often found myself looking for ways on how to include these attractive new tools in my workflow. One such attractive tool was added to the inventory with v1.17 Stable release of Flutter, a couple of days back. No prizes for guessing, I am indeed talking about the NavigationRail Widget.

Anyway, enough talk about artisanry. 😛 Time for us to get down to business.

What is NavigationRail?

Flutter’s first stable release of 2020 brought plenty to talk about. Ranging from Performance boost to massive improvements in tooling. But what caught my eye, was this li’l widget called NavigationRail, which can be termed as a creative alternative to BottomNavigationBar. The both can also be used in tandem to create a seamless experience across devices.

As described here, it’s a material widget that is meant to be displayed at the left or right of an app to navigate between a small number of pages/fragments(if I may call it that), typically between three and five.

The principles and rules are laid out quite explicitly by the Material Design team, but ever since it’s inception, the designer community, especially from Dribbble seem to have taken a special liking for the component and have created some stunning designs using it. Have a look.

credits : leftToRight => @purrweb @tugboi @phamhuyds

Typical Structure

Unlike the AppBar or the BottomNavigationBar, the NavigationRail doesn’t have a specific slot allotted to itself in a Scaffold. A navigation rail is usually used as the first or last element of a Row which defines the app’s Scaffold body.

In the Row assigned to the Scaffold body, the rail takes up the extreme left or right slot. The Main content and the rail is generally separated by a VerticalDivider. Although, the elevation property of the NavigatonRail can also be used for the same. The Main content needs to be wrapped by an Expanded such that it takes up the whole of the remaining space.

Scaffold(
body: Row(
children: <Widget>[
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.selected,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Second'),
),
NavigationRailDestination(
icon: Icon(Icons.star_border),
selectedIcon: Icon(Icons.star),
label: Text('Third'),
),
],
),
VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: Center(
child: Text('selectedIndex: $_selectedIndex'),
),
)
],
),
);

Widget Properties : Explained

Okay, so the designs look cool and we can’t wait to create something like this ourselves. So how do we do it? We’ll try and understand the properties one-by-one. But before that, there’s one class that needs introduction. It’s the NavigationRailDestination class.

What is NavigationRailDestination?

credits : material.io

It is a model class, used to create tappable destinations in the Navigation Rail. It holds the data to represent one destination view/fragment. Mind you, it is not a Widget class. It contains 3 properties :

  1. icon : supposed to take an Icon widget(generally the outlined state) and must always be non-null.
  2. selectedIcon : It’s an optional field, which can be used to provide an alternative( generally filled) state of the Icon. This can be null.
  3. label : A mandatory field, which is to be assigned with a Text Widget. Must always be non-null. The label can be shown or hidden using the labeltype property of the NavigationRail.

The usual NavigationRailDestination object looks like this.

NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('First'),
),

You’ll soon realise why this class was explained at this point.

Let’s start with the NavigationRail properties

  • destinations : This property takes a list of NavigationRailDestination objects. The list should contain 2 or more items. Standard stuff.
  • selectedIconTheme & unselectedIconTheme : Pretty self-explanatory. Using these 2 properties, you can define the color, opacity and size of the Icons of the NavigationRailDestination objects, when they are selected and when they are not. Takes the IconThemeData objects.
  • selectedLabelTextStyle & unselectedLabelTextStyle : Again, using these 2 properties, you can customize the look and feel of the text labels for selected/unselected states. You need to assign TextStyle objects to these properties.
  • labelType : takes an enum class NavigationRailLabelType value, which gives you 3 choices : NavigationRailLabelType.none , NavigationRailLabelType.selected,NavigationRailLabelType.all. Check out the illustration below. It should explain how the property can be used to hide/show the text labels.
Illustration for labelType
  • minWidth : The smallest possible width for the rail regardless of the destination’s icon or label size. The default value is 72. When set to 56 along with labelType set to none, it forms a compact rail.
Illustration for minWidth
  • groupAlignment : is used to set the vertical alignment of the rail destinations. Takes a value between -1.0 and 1.0. The default implicit value is -1.0 which sets the alignment to Top. Value of 0.0 aligns it to the Center. Value of 1.0 aligns the rail items to the Bottom. Arbitrary values between the range can also be assigned.
Illustration for groupAlignment
  • leading & trailing : these properties work in the same way as they do in other widgets like ListTile, AppBar, etc. These take in any Widget objects. Hence in the design below it was easy to nest multiple widgets inside a Column and pass it as leading or trailing. But, I couldn’t get the Trailing Widgets to align to absolute bottom of the Rail when groupAlignment was non-Bottom. It would only trail the Rail Destinations.
  • selectedIndex : The index into destinations for the current selected NavigationRailDestination.
  • onDestinationSelected : is called when one of the destinations is selected. The stateful widget that creates the navigation rail needs to keep track of the index of the selected NavigationRailDestination and call `setState` to rebuild the navigation rail with the new selectedIndex.

That’s all, as far the important properties you need to know are concerned.

The Code

Not embedding the code here directly, as it takes up unnecessary space. You can find the complete code for this screen here.

Some Missing features

Unlike a number of other widgets, the implementation of NavigationRail felt quite full-fledged. With a handful of customization options. Yet one of the most important things I missed, was a global property for Inter-RailDestination padding. I understand it not being there for a BottomNavigationBar, but for a widget which’ll receive tons of vertical space, it would have been nice to have it. I was able to create a hacky workaround for it, but it’s not the most ideal solution. The padding obviously only works when labelType is set to All.

NavigationRailDestination(
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: Icon(Icons.av_timer)),
label: SizedBox.shrink(),
),

Vertical Text Customisation

With the following code you can achieve the Vertical text direction which can be used, even in the Compact mode of the NavigationRail.

NavigationRailDestination(
icon: SizedBox.shrink(),
label: Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: RotatedBox(
quarterTurns: -1,
child: Text("text"),
),
),

Phew, today was supposed to be my off-from-work day. But here I am, having written more than 1300 words for this article, paired with the research, the code, the composition bits and much more. I already had a couple of articles planned, but I took this up because I find this new design pattern incredibly intriguing and I feel the widget to be a wonderful addition to an already amazing library. Moreover, it always feels nice to give a little something back to the community. 👍

If this article helps you, consider clapping 👏 +50, it’ll encourage me to keep investing my time in such community work.

Also, please consider tagging me on Twitter, to show me the wonderful user experiences inspired from this article.

Until next time, see ya. 👋

--

--

Ankit Chowdhury
Flutter Community

3 Million+ Downloads on Google Play, Founder : Mantis Pro Gaming, Veteran @ XDA-Developers. dev’ing professionally since 2013