A Better Bottom Bar
Greg Perry
Posted on February 14, 2024
A Better Bottom Bar
A variation in Flutter’s BottomNavigationBar widget
Flutter is amazing. It allows you options, and you know we developers love options. As I continued my own journey through Flutter, the requirements of my app’s have become more diverse and dynamic in their needs and capabilities. Good thing Flutter and the language it’s written in, Dart, is up to the task. In this article, I’ll look at the widget, BottomNavigationBar. At first glance, its implementation in an app seems pretty straightforward. The following arbitrary collection of gist samples from the Internet would attest to this. Take a quick look at these four samples below.
A common characteristic in all these examples is that all the parameter values for the widget, BottomNavigationBar, are defined and applied to the widget right there where it’s being instantiated — with the exception of the ‘onTap’ routine. Commonly, you’ll find that routine is defined elsewhere as a separate function and or assigned to a variable that is then assigned to the named parameter, onTap. Regardless, all four samples above supply the BottomNavigationBar widget to the named parameter, bottomNavigationBar, found with the Scaffold widget. It’s all pretty straightforward.
Well, what if I don’t want to do that?
What if I want to define and assign any and all the parameter values for the widget, BottomNavigationBar, elsewhere? Different ones at different times — supplying different values depending on different circumstances? What if I want it to be more diverse and dynamic and not so straightforward? Huh? What if I want to do that? Well, in this article, I’ll show you what to do.
Let’s begin.
Explain By Example
As I traditionally do, I like to use Google’s own established ‘samples’ when demonstrating a widget or, in this case, when demonstrating a particular implementation of a widget. And so, we’ll look to the sample app listed with the BottomNavigationBar class as a means to demonstrate the many possibilities allotted to you when developing in Flutter. Click on the image below to view the sample app for the widget, BottomNavigationBar. I would suggest taking a copy to follow along.
Pretty straightforward, uh? Clicking on the three icons on the bottom bar changes the screen — indicating which icon (which index) was last pressed.
The Clutter of Flutter
Now, you know what I mean by the Flutter clutter, don’t you? You too had to get use to the immense list of parameters commonly found in Flutter. If you’re a developer from other platforms and languages, this is not a common practice — the use of long lists of parameters. In fact, it was discouraged when I began programming. However, they’re found in Flutter’s many Widgets — and Flutter is made up of many widgets! And so, when first introduced to Flutter code, you have to get use to reading those long vertical lists of parameters!
And to make matters worse, there’s also the common practice of passing anonymous functions as parameters as well — making it that much harder to read the code. At times, there’s a lot of clutter! A lot of Flutter clutter!
Now take a look at the screenshot below. What do you see? Do you recognize it? Yes, it’s the sample code for the widget, BottomNavigationBar. The little red arrows show you how I reduced what little ‘Flutter clutter’ there was.
Click/Tap on the screenshot’s caption for a copy of the code if you want to run it. You’ll have to get a copy of the class, NavBottomBar.dart, as well to help you along.
Pretty straightforward. You can see the BottomNavigationBar widget is now in a class called, Example. I instantiated that class in the initState() function. As you can see I got a little cute and named the memory variable so to coincide with the getter called, bar. Anyway. Let’s look at the class, Example.
Ok, there’s a lot going on here, but we’ll get through this. Know that this demonstrates the ‘power of Dart’ and how Flutter has implemented its concept of Widgets. Know that the getter, bar, has access to all those getters defined in the class, Example. Know all those getters are named after the very same parameters found in the widget, BottomNavigationBar. Finally, know that all those getters actually correspond to field properties found in the parent class, NavBottomBar. Take a close look at the code and hold on to your hats — we’re going down the rabbit hole on this one.
This code produces the exact same results. However, now your code is a bit more modular. All the code concerned with the ‘BottomNavigationBar widget’ is self-contained within one class. And so, when you display this bottom bar. the code is a little cleaner. There’s not as much Flutter clutter. It’s been replaced with bottom.bar. A little easier to read. Now, what‘s this class, NavBottomBar, you ask? All in good time. Let’s continue.
Not Just Clutter
Now it wasn’t just the clutter that prompted all this. Remember, I like options. And so, let’s take a look at the screenshot below. It uses the class, Example, again and produces the exact same results as the original sample app, but things have changed. What do you see?
This time I‘m not passing the state object to the class, Example. A lot of things have changed, but it still defines the List of items in there to keep things easy to read, and it still extends that mysterious class, NavBottomBar.
However, you see those other *getters *are gone from the Example class and their properties are instead outside of the class being assigned values in different parts of the code. They’re not all being assigned values in one spot.
Now why would you do that, you might ask? Well, when your catalog of apps gets bigger and more complex, you’ll find at times, it’s not as readily easy or convenient to define all ‘the attributes’ to your BottomNavigationBar in the very spot it’s instantiated. You’ll find it would be better if you could define those parameters in different places and at different times within the code — and, in fact, have those values change over time over different circumstances! It’s hard to demonstrate this with such a simple example, but I’ll give it try.
Let’s look at that example once more. It’s listed again below. Note, the class, Example, is not instantiated in the initState() function this time. Why? Because! That’s why. You will have apps where the BottomNavigationBar has to be instantiated elsewhere because of some required specifications — and with this implementation, you’ll at least have that option. Believe me, it’ll happen.
Look at the ‘bottom bar’ properties below. You can see in this configuration, you have the option to define the **onTap() *routine and the ‘selected colour’ in a place prior to displaying the bottom bar. Note, further in this configuration, the property, *currentIndex, resides in another place, in the **build() function. It’s there to be updated by the variable, _selectedIndex. You can readily see everything. You can understand what each property does. You’ve got options.
Let’s try another one. Look at the screenshot below. Things have changed yet again. This time the Example class is back instantiated in the initState() function. However, this time, those properties are now named parameters and are being supplied at the spot where the class is instantiated! Hey, why not?! It could happen. You’ve got options.
There’s another change as well in the build() function. That getter, bar, has been replaced with the function, show(). The show() function is assigning the new ‘current index’ in the build() function when it’s changed by the user. See how that works? In this configuration, the List of items continues to be defined within the Example class and that class continues to have that parent class, NavBottomBar. See how the potential is there now to change the properties of the BottomNavigationBar right up to when it’s shown?
You can also see, in this configuration above, the Example class is merely passing those parameter values to its parent class, NavBottomBar. This parent class is obviously taking care of things when the function, show(), is called. Of course, it’s been taking care of things in all of these configurations so far.
If it hasn’t already hit you yet, that show() function means you’re able to ‘override’ any and all parameter values you may have assigned when you first instantiated that class or when you assigned values to its properties elsewhere in the code. Boom! Options! Let’s keep going.
Here’s another example. Let’s see what we could do with this new-found ability. How can I take advantage of the fact I’ve access now to any and all the properties of the BottomNavigationBar right up to when it’s displayed?
Well, I’ve added a GestureDetector widget to the sample app. Now, if I tap on the ‘index label’ displayed in the center of the screen, the selected bottom bar icon will change its color from amber to red. That’s because I now have easy access to the ‘bottom bar object’ and its properties. See below.
Now, would you ever need to do that one day? You’d be surprised. Now that’s just one property. You’ve access to all the properties that make up the BottomNavigationBar widget. Even the widget itself. Think of the possibilities. With this implementation of the BottomNavigationBar widget, you’re now able to do a lot. For example, notice that commented outline, bottom.hide = !bottom.hide. It involves a property called, hide. Now that’s new?! That’s not even found as a property in the BottomNavigationBar widget!? Now, what does that do, I wonder? Options.
A Class By Itself
Do note, this parent class is not an abstract class. It’s a class in itself. And so, if you want, for example, to emulate the traditional way of using the good old widget, BottomNavigationWidget, you could do that. Easy peasy. See below.
There you go. Pretty straightforward. We’re back to the old approach of supplying everything in one spot. Of course, now that you’ve seen the potential — kinda boring approach, uh?
Keep Current
It’s somewhat ironic that this simple example is all about displaying the ‘current selected index’ in the center of the screen when the user taps on the bottom bar. It relied on that integer variable, _selectedIndex, to make this work.
The configurations may have differed, but the fundamental process had this variable, _selectedIndex, updated with the selected index, and then, when the function setState() causes the build() function to be called again, that variable is assigned to the elementAt() function to display the current index in the center of the screen as well as to the BottomNavigationBar property, currentIndex.
Now as a last example, take a look at the screenshot below. It again implements our mystery parent class and again supplies the very same results, but there’s something missing. It’s that extraneous little variable, _selectedIndex.
Notice there isn’t the use of the named parameter, currentIndex, either in this example. The ‘bottom bar’ object doesn’t need to be assigned the current index because it always knows the current index! Notice the ‘onTap’ routine merely calls the setState() function to update the screen. The original widget, BottomNavigationBar, did not retain its ‘current index’ as such. A characteristic I personally found annoying when working with that widget. Of course, it didn’t have the means to retain its currently selected index. With this implementation, it does. Note, the function, elementAt(), simply looks to the ‘bottom bar object’ itself for the current index. Also note, all those past examples didn’t need that variable either. It was just a little extra work for nothing. Sorry.
Ok, one more example. It’s one I made up, but I was going to leave it out. However, it does demonstrate once again the new-found ability to manipulate the BottomNavigationBar seemingly ‘on the fly.’ Now that you have this ‘bottom bar object’ to work with, you can change the widget's attributes depending on other circumstances. In this case, depending on how many times you tap on a bottom bar icon. You’ll now see a little green at times as well as amber and red.
I know. It’s not too thrilling. Just another variation. Notice, there’s no variable, _selectedIndex, anymore. It was never needed — and there’s that commented out line again, bottom.hide = !bottom.hide. What does that do?!
While Under Development
I’ll quickly offer one of my own apps I’m currently working on as an example. The screenshot below demonstrates one means of navigating between screens. There are three screens in this case, and one has yet to be picked. You pick one by selecting its icon listed along the bottom bar. Those icons are defined in a separate class called, StockStatBottomBar. Keeping things a little more modular; a little cleaner.
You see below, if the variable, view, is null, a Scaffold widget is instead called to display that bottom bar. Note the Scaffold is wrapped in yet another mysterious class called, StockStatsScaffold, but that’s another story.
Allowing for the BottomNavigationBar widget to be wrapped in a bottom bar object makes Life just a little bit easier in this example. The variable, view, is defined within this stretch of code, but that’s ok, the bottom bar object allows for easy access to that variable — defining its ontap() **routine in the **initState() function.
I can’t get too particular with this example. It’s a ‘for profit’ project after all — not ready for ‘prime time.’ It won’t run on its own.
What’s Under The Hood
Let’s finally take a look at this parent class, NavBottomBar. As you may have already guessed it’s a utility class I keep in my ever-expanding toolkit of functions and features that I use to make my Life easier when developing software. In time, you no doubt will have such a toolkit yourself.
It’s a pretty long and messy-looking class. Such utility classes often are. Lots of repetitive typing and pasting was involved, but once done, such a class serves to, again, make things easier. It now provides more options when working with the BottomNavigationBar widget.
Hello there. You made it. How about we take a closer look at this class? Let’s break this class up into sections and see how Dart makes such a class do what it does in those screenshot examples above. There’s really nothing to this class, is there? It’s just a lot of repeated lists of parameters here and there, and then calling the BottomNavigationBar widget in a function called, show(). Not much to it at all. It’s enough, however.
TL;DR
If you want to, you could just take a copy of this class and get on with your Life. Simply tap on the screenshot above to get to a gist copy of this class. Take the class and make it your own. Make it better and then share it. Or, you can stick around, and see what little was involved in making this more diverse and more dynamic bottom bar.
We’ll start at the beginning. Know that the list of parameters for the class, NavBottomBar, pretty much mirrors that found in the class, BottomNavigationBar, including the assigned default values. The only addition is that field, hide. Note, it’s set to false by default.
We skipped the list of declared fields since they’re already listed above. We’re now looking at the getter, bar, and function, show(). Will this getter remain? I don’t think so. I’ll likely drop it to keep consistent with what you’ll find in Dart and in Flutter itself. I mean, the use of a getter in that fashion is not a common occurrence in Flutter. The function, show(), explains itself better if and when another developer comes upon the code. Don’t you think?
As you see above, the show() function also has a long list of parameters. Of course, it means when maintaining such a class as this, if and when the widget, BottomNavigationBar, adds a new parameter(s), there will have to be an update to this class. It’s the price one pays for some diversity in your code.
Note also, further on in the function, how the field (or class property), hide, is checked for null. That’s in case it was directly assigned *null *by a user for some reason. It could happen, but that field must be a boolean, and again, it defaults to false.
Those double question marks (??) are formally called the null coalescing operator. With the addition of the equals sign it’s then called the null assignment operator, and the following statement, this.hide ??= false;, is the equivalent to, if(this.hide == null) this.hide = false;
It assigns a value to the variable only if the variable is null. Further, the ‘if null’ operator also serves as a conditional expression in the Dart programming language. And so, when you see an expression like the following:
var test = items ?? this.items
That means if the variable, items, is non-null, it returns its value; otherwise, it returns the value of the field, this.items, and assigns that to the variable, test. You’re going to see the ‘if null’ operator used a lot in this class.
Next in the code, we’re instantiating the widget, BottomNavigationBar, and you’ll see the onTap() *routine is being defined. Look in that routine how the ‘currently selected index’ is always assigned to the class field, *currentIndex. This keeps track of the current index. It’s even recorded again in an internal variable, _lastIndex. That’s in case a user directly assigns an ‘invalid’ index value to the currentIndex at some point for some reason or another. Again, it could happen with such utility classes. The _lastIndex variable corrects it. Finally, you can see in the **onTap() routine, if the user passed in their own ‘onTap’ routine, and it is not null, it too is supplied the current index and runs.
It’s all coming together, isn’t it? Granted it’s messy looking but the remaining parameters for the widget, BottomNavigationBar, are then supplied below in the next section of the class. Here, you can see the ‘if null’ operator used, again and again, to ensure a non-null value is passed to most of that widget’s many parameters. And there you are. That’s a quick tour of the mystery class.
I know, there are other approaches to all this. For example, it’s common to just assign to a variable a ‘varying’ BottomNavigationBar widget and just assign that variable to the Scaffold’s named parameter, bottomNavigationBar. Changes are done by assigning a different widget to that variable. The class, NavBottomBar, maybe a bit of overkill in some respects, but essentially that’s what it’s doing anyway every time its show() function is called.
I’ve found such a class is useful for my projects, and that’s just for the widget, BottomNavigationWidget. A pretty straightforward widget to begin with. I’ve done this to others…but that’s for another time. Finally, don’t forget to try out that ‘hide’ property. Bet you can already guess what it does.
Cheers.
Posted on February 14, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.