Originally published 2018-02-17 on blog.tlarsendataguy.com, and shared here with permission.
How many ways can you handle navigation in a MaterialApp? I count at least 3. Option 1 is to provide a route map in the MaterialApp constructor:
Option 2 is to use onGenerateRoute:
Both of these options can be utilized by calling Navigator.of(context).pushNamed() from our code. We can also just call Navigator.push from our pages to perform navigation in a decentralized manner:
All of these methods can be mixed and matched to meet our particular needs. After playing around with this for a while I finally settled on using onGenerateRoute (the second option). I chose this option because I will need to handle somewhat complicated navigation scenarios. Performing checklists in the app will be simple; in fact, I intend to do it all on a single page which updates its state as the user checks off each item. The editor, however, is a different story.
A quick note on the status of the app. When I build an app I start by completing, as much as possible, the back end model classes using a TDD approach. I like to get everything working before I begin building a single page. This helps me separate the core logic from the UI code so that, ultimately, the UI code’s only function is to display the model and translate user input into actions the model should perform. As of now the model classes for my app are built and their tests all pass. The structure of the model classes is as follows:
For editing checklists I have decided on using a path string to navigate through the various elements. The paths take a form like this:

Initially, I created a ParsePath class which could take a path string and return the corresponding object. My checklists are stored as JSON text files, so the ParsePath class needs to read the appropriate file, instantiate the object tree, and search through it to find and return the correct item. Each page does this itself; I do not pass object references between pages, preferring instead to rebuild the object every time a page loads. I am not sure if this is entirely the ‘right’ way of doing it, but it feels much cleaner and alleviates some concerns I had about state management in response to lifecycle events. This is what the initial version looked like when it could only parse Books and Checklists:
The main application class (i.e. main.dart) did not use this code; rather, it was called during the construction of the EditBook and EditList pages, like so:
The navigation code on the main application class looked like this:
Yikes…I am kind of embarrassed to post that. There is lots of duplicate logic here. It started to become very tedious to add new layers of navigation because I would have to do it in 2 different places. The ParsePath class was under test so at least I could be fairly sure it was working, but the UI code was not. I started to find excuses to not work on the navigation of my app because looking at the code filled me with a sense of dread…or maybe defeatism. It is a bit hard to explain, but I felt like I didn’t have the energy to look at it. When that happens, it is a huge red flag that my code smells and I need to re-think it.
So last week I sat down and re-designed my app’s navigation process. I moved all of the validation logic to the ParsePath class, got it under test, and put the main application class on a diet. I also changed how I return values from the parse so I only need 1 public method to perform the parsing. Having multiple parse methods was starting to bother me. The current ParsePath code looks like this:
And the current version of the navigation code looks like this:
I don’t know about you, but I think it is much cleaner and easier to maintain. Adding a new page means creating that page and adding an additional entry to the switch statement in _getRoute. Everything else will just work. The ParsePath class still feels a bit large, so there is probably some opportunity for optimization, but it works right now for parsing every path my app will need. It won’t grow in size at this point, and I can come back later to tinker with it and distract myself when some other code in my app is filling me with a sense of dread.
Also, did you notice the recursion in the _validateItem and _parseItem methods? I do not know why, but I always get a bit giddy when I find a practical use for recursion. Last stupid question: would you say there is a hint of functional programming in the design of the validate algorithm? Like, if I was doing this in Lisp I would have a bucketful of parentheses at the end, right? Anyway, not important. Now that the navigation is fixed my next focus will be creating the pages necessary to edit all aspects of the checklist. More to come!


Responses