Photo by Milivoj Kuhar on Unsplash

From Flutter to Flight 6: Remodeling

It’s been a while…where are we? Something about building a checklist app, I think. Progress has been slow, partly because of an uptick in demand from my day job. But mostly, I have been struggling a bit with motivation. Seeing the Flutter community grow and mature has been a fascinating thing to witness. Every day someone releases an amazing widget, tutorial, or framework that really showcases the power and flexibility of Flutter. There is so much amazing stuff happening from lots of very talented people. The Flutter weekly newsletter seems to get longer by the week, and it is always a great read.

And then there’s me, doing my thing, which isn’t particularly sophisticated, elegant, or shiny. In a strange sort of way, seeing all of the incredible work being done makes me realize just how far behind the curve I am. Unless I start doing this for a living, do I really have much chance to catch up? It is hard to say, but the question has resulted in many mornings where I boot up my laptop, open IntelliJ, stare at the screen for about 2 minutes, and then shut it back down again.

So what to do? I spent several weeks in this cycle and made very little progress. Instead, I had to reflect a bit on my reasons for dedicating time to this project and what I really wanted to get out of it. At the end of the day, I decided that no one else is writing a checklist app. Even if someone was, I would likely disagree with the design. And ultimately, coding, for me, has always been about having fun building something rather than some sort of ego-driven status symbol. Even if my logic is poorly written or the UX is less than enjoyable, it will be fun creating it and I think I will learn many things along the way. So I have decided to continue pushing forward and keep writing about it, even if for my own amusement.

Alright, that’s enough personal introspection for one day. Let’s move on to some code.

At the conclusion of the prior post I had finished the main components of the checklist editor. I had three options for moving forward:

  1. Add some sort of user management to the app.

Options 1 and 2 feel hard and I still do not really know where to begin. Option 3 is the easiest because I can stay at the client level and keep using Flutter. Naturally, I went with option 3. One of these days I will have to answer the call of options 1 and 2, but it is not this day.

Design-wise, I want the ‘usage’ aspect of the checklist app to be contained on a single page. I wrote some business logic a while back that handles the forward and backward navigation through a single checklist’s items. The Flutter page should simply latch onto this existing logic and refresh itself when a user moves forward or backward through the checklist. This means I will need to intercept pop commands and only allow the navigator to pop when my business logic runs out of its internal back stack.

As a reminder, this is the high-level design of the data structures in the app:

PowerPoint, the poor person’s diagramming tool since 1987.

Before I touch Flutter, I need to finish the navigator logic. This shouldn’t take long. The business logic I wrote only navigates through a single checklist and the code is all contained in the Checklist class itself:

The key methods here are:

  1. nextItem({bool branch}), which moves forward through the checklist. If the current item is a yes/no question, then branch must be provided to tell the Checklist which collection of sub-items to navigate through.

The logic is fairly complicated because it has to potentially deal with nested yes/no questions. It was written using TDD, which is the only way I have any confidence it actually works. With all of this code, a single checklist can navigate forward and backward through its items.

But the navigator logic also needs to be able to navigate between checklists so that a full flight, from preflight to post-landing shutdown, can be accomplished seamlessly. I suppose I can add something to the Book class to handle this; that shouldn’t be too har…

…you know what? Book is already complicated enough; I do not want to add more to it. Perhaps a separate class? Let’s call it Navigator, because I love creating naming conflicts with core Flutter classes. We will require a Book object and will assume the first normal checklist is the starting point of navigation:

class Navigator {
Navigator(this.book) {
currentList = book.normalLists[0];
}
final Book book;
Checklist currentList;
}

Now that we have a basic class, let’s take a moment to think about the implementation. I suppose we can delegate all of the work of navigating back and forth through individual items to the Checklist class. The Navigator class should hold a reference to the current and prior checklists and provide methods for navigating to a new checklist. It should also back-navigate to the prior checklist when the current checklist can no longer go back. I am making a conscious decision to limit backwards navigation to 1 checklist in the past. My thinking here is that there should never be a need to go back more than 1 checklist because time generally flows forward and checklists are pretty tightly bound to time. This is subject to change based on real-life usage and needs.

One issue that I am seeing with this design is the handoff between Navigator and Checklist. Right now these two do not talk to each other. How do I integrate them together? Should Navigator have a getter for the current item that simply reaches through currentChecklist? If so, then I start to duplicate the tracking logic in the Checklist class. I will have to add getters to identify whether Navigator can move forward or backward through the current checklist. Almost the entire public interface of Checklist will get duplicated in Navigator. That seems…wrong. Maybe I should let Navigator focus solely on checklist navigation and let Checklist focus on item navigation. But if I go with this design the caller would have to juggle between these two classes. Each interaction would require seeing whether Checklist can move forward, and if it cannot, then switch over to Navigator to move to another checklist. That feels a bit like needless complexity. I really want a single point of interaction for all things navigation.

Ugh…both of these options suck…

…and that’s a red flag to me that I may have screwed up the existing design. Looking back over the Checklist class makes me think I violated the Single Responsibility Principle. Checklist should be managing a collection of items; it really shouldn’t be concerned about how those items are navigated. Let’s strip out the navigation logic from Checklist, merge it into Navigator, and create a single, coherent class to handle all things navigation.

We have the moveNext(), goBack(), and playHistory() methods moved from Checklist to handle the detailed item navigation. goBack() has been amended to navigate to the prior checklist when we reach the beginning of the current checklist. A new method called changeList() will save the current list and navigation history into the prior- fields and navigate to the new list. This class should do all of the navigation activities I need to accomplish in the app. Even better, Checklist has been greatly simplified and is easier to reason about. I was able to move all of the old tests into a new test file and add new ones for the additional logic. Everything is working and, while there may still be opportunity for further refactoring, it is fairly easy to grasp what is going on.

Yes, it feels like I am in a better place here. A single object will handle all of the forward and backward navigation. The methods to navigate forward through items and checklists are still separate, but I think they need to be because I want my users to be able to switch to a different, unrelated flow. For example, if the engine catches fire they should be able to navigate to the Engine Fire checklist, even if the current checklist has no links or references to it. The current design feels like an improvement because the client classes can do everything they need with a single object.

Another benefit I see is that future work on navigation logic can happen entirely in this one class, so future changes should be easier to perform. Over time I can make this design better, whereas with the prior logic I think it would become worse over time.

*whew*

Now that the business logic has been remodeled I can move back to Flutter world. I was not expecting to do this much work with the back end code, but it was an important step in the evolution of the app. The next post will focus on creating a page in Flutter which will interact with Navigator and allow us to actually use the checklists.