How to create an all-in-all bottom navigation bar in Flutter under 150 lines of code

Deep Patel
Flutter Community
Published in
4 min readJun 16, 2020
Photo by Heidi Fin on Unsplash

I am have been testing with Flutter for about a month now and a few days ago, I ran into a massive problem. How to get the bottom navigation bar to stay fixed at its position when a new screen is pushed onto the stack?

Surprisingly, Flutter documentation has an example of a single screen for each tab, which is unheard of in any real-world applications. There is a very good chance that’s not what you are looking for, plus it doesn't even work the way it is supposed to.

After wasting 2 days googling…

credit: https://tenor.com/tNMU.gif

I stumbled upon Andrea Bizzotto’s workaround for this problem. Here is an improvised version of his workaround.

What are we trying to achieve

  • fixed bottom navbar(obviously)
  • persistence of state, e.g. children routes in view, scroll position, etc, while switching tabs
  • handle android’s back button
  • full navbar customization capabilities — I believe business logic should not limit you from creating the perfect looking app
  • follow DRY principle

Let’s begin by creating the file tabItem.dart and copy the following code

Each item in the tab bar has an associated page that will be wrapped around a Navigator and Visibility widget. Visibility widget is optional but it removes unnecessary problems like interactivity and animations when the page is inactive.

There are more than 5 keys, each designed for different use cases. In our situation, we are using the global key because they allow us to manipulate widgets without losing state. So, when you turn to different tabs, the global key plays an important role in preserving the state such as the scroll position or maintaining the input value. You may also want to look into PageStorageKey which has a very similar use case. You can learn more about keys and how they work here.

In app.dart

AppState constructor handles indexing each tab item. You can supply index as an argument in the list of tabs but I wouldn’t suggest you do that because every time you change locations of any tab, you would have to manually change those numbers as well.

WillPopScope is responsible for handling behaviors related to android back button, returning a false value prevents the user from exiting out of the application.

We are using IndexedStack so only one widget from the list is shown at a time. Also, index argument must not be null otherwise nothing will appear on the screen, you might want to add null safety check.

If you want your scrollable content to be drawn behind the tab bar like this

set the extendBody argument to true in scaffold but make sure to add extra padding at the bottom of the body in order to prevent hidden content.

HomeScreen and SettingsScreen are mockup screens — screens.dart

Onto bottomNavigation.dart

I am using built-in widgets but you can customize the way you like your navbar to look like.

finally, in main.dart set the home argument in MaterialApp to App()

Here is what the final product looks like on my phone

Congratulations you did it

credit: https://tenor.com/YsZP.gif

What did I change from the original post

  • Offstage → Visibility
    Offstage has a big disadvantage. Offscreen pages might not be painted but they remain active meaning animation consumes CPU and battery and more importantly, they can receive focus and have keyboard input directed to them. Visibility widget takes care of all these problems — disable offscreen animations, prevent painting, remove accessibility tools, and block interactivity.
  • Stack → IndexedStack
    Only show one child at a time rather than stacking them up
  • I found some of Andrea’s code redundant. I had to make a whole bunch of changes just to add a new tab. Also, there is an absence of comments around confusing bits of code.

Lastly, a few things to keep in mind

  • Sadly this workaround comes at a price. You won’t be able to use get navigation package or perhaps any third-party navigation libraries that utilize context internally, without you having to pass them down as a parameter
Navigtor.of(context).push(...) // this will works// from get package
navigator.push(...) // no bueno
  • I didn’t use any kind of global state management so developers of various levels can understand, but I would recommend, so you access them from anywhere in the widget tree. Prop drilling wouldn't serve as a viable option for either long term or big projects.
  • I advise you to check out the original post as it contains more technical aspects and explains in detail how this solution works.
  • By no means, I am an expert at Flutter, so, please free to leave a comment if you know a better solution.

Thank you and have a great day

Source

https://www.twitter.com/FlutterComm

--

--

Deep Patel
Flutter Community

Flutter and React Developer, UI & UX designer, UNG student Instagram: @thisisreallydeep