Wait, am I done with all of the editor pages already? That…went faster than I anticipated. I guess this means I have to start thinking about the rest of the interface, integrating the app with a back-end service, and user management. I have been putting these topics off because they are kind of hard and I do not yet have clarity on how I want to do them. But we will talk about all of that in a later post.
As I was creating the editor pages, I realized that I could re-use some of them. Several of the editing ‘contexts’ in the app deal with the same type of objects. For example, a Checklist and the true/false branches of an item both manage a list of items. So with some minor adjustments I was able to code those pages to properly plug into the right context.
The last couple of pages went by quickly after that and now the editor can do everything I need it to. I have been adjusting and de-bugging the pages and…wow, the pages nest into a deep hierarchy. There were a few times I lost track of where I was. I figured I could either re-design the interface to reduce the amount of nesting or I could add a navigation overview widget to the right of each editor screen. The widget is easier, so of course I went for that.
I thought I would start by creating a quick sketch of my vision, but no one wants to see my drawings, myself included. So I decided to do a mock-up in Flutter itself. That actually went very well and I ended up mocking it right into my app rather than using my usual Sandbox project.
I was originally going to make a static mock, but it took so little time that I decided to write up some code to dynamically generate the navigation bar items on each page. Because I previously refactored my editor pages into a higher-level abstract class, I was able to do this all in one single place. I just had to add a variable for a list of widgets to my
…and then populate that list in
initState() using the path that was passed to the parent widget:
I know, I know, not the most elegant code. That switch statement in particular is a bit ugly. I have not refactored anything and this is definitely not the final code. The icons I chose are placeholders and I may end up making changes to some of the business logic classes to better provide the information that this class has to calculate itself (and get the logic under test!). With this ugly code in place, it is a simple matter of re-arranging the
buildEditorPage() method to accommodate the new navigation bar:
Ta da! Now all of my editor pages have a navigation bar! Cool, right?! Inheritance can be great, sometimes. But I decided that I was not done yet. I want to be able to click on these icons and navigate back to that page. This is way beyond the scope of my original intention, but things have been stupid easy to this point, so I figured, why not? In order to navigate back to an arbitrary page in the navigation stack I would need to utilize the
popUntil() method from the
popUntil() takes a
predicate parameter. This parameter is a function that is given a
Route object and it is supposed to return true or false. If true, the navigator stops popping.
The problem is, what can I use that is inherent to a
Route object to identify when I need to stop popping? Looking at the source code (side note: open source is awesome!), I saw a
currentResult variable that is hard-coded to null. Can I somehow mangle this variable enough to serve my purposes? Yes, yes I can. Coding may be a hobby, but I can mangle with the best of ‘em.
So I did what you always do in an object-oriented language: I inherited the class and overrode the living daylights out of that variable. Here is perhaps the shortest class I have ever written in my life:
All I want my custom route to do is to store the level that represents the current path. A path like
/book/normal would be a level 2 while a path like
/book/normal/list/0 would be a level 4 (if you have trouble getting 2 and 4, split the path on ‘/’ and get the index of the last item).
If you remember my app’s navigation methodology from the third post, then you will recall I use
onGenerateRoute to perform dynamic named-route navigation. Integrating my custom route into this scheme was very easy; I simply adjusted the
_buildRouter method to calculate the level from the path and then return my custom route instead of a standard
Now that the routes are able to identify their level in the
currentResult variable, I can add a method to my navigation IconButtons that will pop until a specific level is reached. I did this with a function that takes a level (an int) and returns the necessary
onPressed function (seriously, what did I do before discovering functions could return functions? It was the dark ages):
Ok, I know what we have just covered seems like a lot, but all of this happened in the course of 10–15 minutes. I was floored by how quickly I was able to prototype my navigation bar, and I didn’t even have to draw anything. So I decided to take it one final step further. The navigation bar is working, but it does not have any animation. In fact, it enters and exits with the rest of the page rather than staying put, which introduces discontinuity to the whole editing experience. I want to persist and animate my navigation bar, and I think I want some heroes to do that.
If you do not know what Hero widgets are, do what I did and read up on them here (seriously, Flutter has some of the best docs I have ever seen). This is my first time trying them out and I found them remarkably easy to use. After a few iterations I have just about everything I want for the navigation bar. But first, let’s talk about the changes I had to make.
The first major change was to convert the
Column which originally stored my navigation widgets into a
Stack. I did this because the square which highlights the current navigation element has to be completely separated from that element so they can animate differently. I essentially need to draw it on top of everything, which means using a
After that, each item, as well as the highlighter, has to be converted to separate heroes. One last hero is required, which is a black opaque box that sits at the bottom of the stack. This box prevents the underlying page from ‘bleeding’ into the navigation bar when the navigator pushes or pops. Put it all together and the
initState method changes to look like this:
There is lots of duplication here; I haven’t cleaned anything up yet. Don’t worry, I will get to it, it is bothering me as we speak. But the key point is, this gets me about 95% of where I want to be, and I haven’t even had to touch animations. This took me a bit more time to get right, but still far less than I expected. In the course of an hour or two I went from a hazy idea in my head to a very nice and functional custom navigation overview using something I have never used before. With that kind of efficiency and ease of use I can afford to experiment and try crazy (for me) things.
I really, really love Flutter.
But enough talk, let’s see the navigation bar, as well as the entirety of the editor, in the following video:
So, that is where the project is at. I still have lots of big things ahead that need to be done. But one big item, the editor, is in a state of completion. Going forward, I will be using the editor to create actual checklists as well as moving forward on other elements of the app. For now, if you want to see the entire source code of the app and delve a bit deeper, check it out on GitHub.