From Flutter to Flight 4: Functions and Refactorings
How many of you are proficient, or at least familiar, with functional programming? Raise your hands, don’t be shy. Great, all of you can leave now, because I am probably going to say something stupid in this post.
I don’t ‘get’ functional programming. I have tried both Clojure and R and found them generally indecipherable. This is not to say they are bad languages (well…maybe R is), only that I personally cannot use them productively. But I think this may be a case of alien syntax obfuscating the underlying concepts. Lots of smart people use functional programming to great effect and I have had a nagging feeling that I am missing something profound, or at the very least a nifty tool for my toolbox.
I recently came across this article and had somewhat of an ‘aha’ moment. The revelation was the idea of functions returning functions which, besides being a bit meta, was something I really never thought about doing. The rest of the article started going over my head, but I figured if I could at least implement functions returning functions then I would have incrementally improved my capabilities. So I started to look for opportunities to do this in my Checklist app, and found one right away.
The checklist app’s top-level object is a book of many checklists. Each checklist points to the checklist that comes after it so they can be properly completed in sequence. The checklist editor page needed a way to select which checklist comes next. I did this in Flutter using
showDialog embedded in a function I called
chooseList. The function returns a
Selection object, which is simply a wrapper for the chosen list. If the return value is null then the user cancelled and I do not make any updates. Otherwise, I update the checklist’s pointer with the value inside the
Selection object. I do it this way because providing a null pointer is a valid action but I need to differentiate between a null pointer (a
Selection object with a null
Checklist) and cancelling (a null
Selection). Some code to help you visualize:
This is all well and good except that the book object has 2 sets of lists: normal and emergency. This dialog only allows the normal lists to be selected:
Ideally, I want the dialog to have a tab control at the top to allow me to switch between normal and emergency lists. In the past, I would have duplicated the
buildBody code and cobbled together this functionality, unhappy with the duplication but not sure how to do it differently. Not today! Today, I have learned a new trick; I can build a function to return a function! I do not have to copy and paste! The code below is how I went about it. I took me about 5 minutes of staring at the screen and going ‘huh?’ before my brain did the necessary gymnastics to start typing it out.
_rootBuilder function is basically just the original
buildBody function, but it returns a function now rather than a widget. The
_emergencyBuilder functions also return functions…really, all they do is transform the generic
_rootBuilder function into a specific implementation for normal and emergency lists. They are called from a revised chooseList function that now has tab controls:
And this is what it looks like:
Now that I have done it I find this pattern is becoming easier. I no longer have to stare at the screen looking stupid for 10 minutes before I figure out what to type. Progress, I guess?
At the same time I was fearlessly charging into Functional Programming Preschool I was also building various editor pages for my app. After creating about 4–5 pages that use a lot of the same code, it finally occurred to me that I should probably refactor that code into its own abstract class which I can extend on each concrete page. I get the impression that most developers will refactor as soon as they write the first duplicate. Sadly, this awareness is not yet strong in me.
All of my editor pages need to perform several common actions:
- Load a book file into memory from the disk.
- Change the theme between red and green (someday I will talk more about this and why I am not sure I am doing it right).
- Persist a book to the disk when a change is made.
- Several of the pages need to be able to handle
onMovecallbacks when the user changes the order of items in a list.
Let’s go through these one-by-one as I implement them in the abstract class. Let’s start with the class declaration and the file load process:
There is a bit here, so let’s break it down.
EditorPage is simply a
StatefulWidget that takes a string argument representing the navigation path. Easy enough.
EditorPageState is a
State<EditorPage> and is where all of the work is actually performed. It is created with an
isLoading flag and an instance of a
BookIo is the class I created to handle writing and reading book objects to and from the disk.
initEditorState() is intended to be called during
initState() on the concrete implementations of
EditorPageState. It’s purpose is to parse the navigation path and save a reference to the parsed
Book. It accepts a callback which allows concrete implementations to do whatever they want with the other parsed objects. It is synchronous to make it easy to use with
ParsePath.parse() is asynchronous because it reads from the disk. Hence the need for the
buildPage() is meant to be called during the concrete implementation’s
build() method and provides the ‘frame’ for the page’s content. Initially, it only builds a
CupertinoActivityIndicator. This is intended to provide visual feedback that the page is loading the
Book file. Once the file is loaded then the page rebuilds and displays whatever content was passed in the
That’s it for the first common action of loading the book file into memory. This process is what caused me to start refactoring because it was getting very annoying to repeat on each page.
The second action, changing the theme, does not require much additional code. First, I adjusted the
EditorPage class to accept a special callback for my theme-changing process.
I then created a small method to call
setState when the theme changes and pass it to the
themeAppBar calls this when the appropriate button in the app bar is pressed.
The next common action, persisting a book to disk, has one caveat. If persisting somehow fails then it needs to undo the action. I use a modified Command pattern in all of my business logic objects, so it is trivial to implement here. All I need is a stand-alone function each concrete implementation can call when it wants to save the
Last, but not least, is the ability to handle reordering items in a list. To do this, my app currently uses a drag-and-drop
ListView which I created myself. It calls an
onMove callback when an item is moved. If you want to check it out, take a look here. I keep working on it when I have time, but I am secretly hoping the Flutter team comes up with a more robust (correct?) solution of their own that I can replace mine with before I release.
DraggableListView handles the moving for me, I just need a small method to actually make the change in the source list and persist the
Book to disk:
Putting it all together looks like this:
When I create a concrete implementation, I simply extend these classes and add the necessary hooks in the
build methods. Then I can focus my energy on building the particulars for that specific page.
Going forward, I will be continuing work on the editor pages. There are a lot of elements which can be edited so I have a lot of pages to create. I will also be looking for additional opportunities to refactor my work and use functions returning functions. I am also excited to try out the new Dart 2 preview; there are some really cool changes coming with Dart 2.