
Photo by Pixabay on Pexels.com
A few weeks ago I started a project to rewrite my app Pray On The Go using Flutter.
First I implemented the persistence methods as this would make testing the rest of the app easier. Even though this is local file storage, I used json for persistence anticipating that I will leverage the same format when I eventually start serving up prayer requests from a server.
The json encoding and decoding libraries for Dart (the language used with Flutter) have undergone a lot of change in recent years and it turned out to be some work to figure out the best approach for handling decoding. Much of what is written about json decoding for Dart is outdated because of the changes that have taken (or are taking) place.
The code for creating my classes from json turned out to be simple once I properly understood the model that could be used for parsing json and reviving classes. I just created a fromJson constructor for my primary object class and then created a revivor method that could be used on the list of objects, like this:
dynamic revivePlaybackList(dynamic key, dynamic value) { if (key == "playbackElement") { PlaybackElement pbe = new PlaybackElement.fromJson(new Map.from(value)); pbe.index = this._playbackList.length; this._playbackList.add(pbe); return pbe; } if (key == "listName") { assert(listName == value); return value; } return value; }
The rather straightforward task of turning the file into a list of objects can then be handled like this:
_jsonCodec = new JsonCodec.withReviver(revivePlaybackListList); String jsonData = await _file.readAsStringSync(); await _jsonCodec.decode(jsonData);
Happy with my progress on implementing persistence, I wasn’t quite prepared for the difficulty that would come next as I tried to get the screen to update with my changes as the file loaded.
Since I was new to Flutter, I had started off with a simple list example app and then was modifying it both to learn and to implement the functionality I needed. But with my superficial understanding of the model for updating a screen of widgets, I quickly met with frustration as all of the things I tried seemed to have no effect. My mental model wasn’t only superficial – it was broken.
I had to take time out and really understand the stateless and stateful widget tree model that the creators of Flutter designed. In addition to understanding this model, there are nearly 150 widgets defined with about 500 classes that can be combined in all kinds of helpful and unhelpful ways. So the examples I found to help with this process were key. I ended up spending a couple of weeks learning Flutter and trying my hand at turning a simple sample app into something useful.
Once I had that behind me, I started fresh, with better understanding and with more appropriate examples. I’m nearing completion and have put in 80 hours since this fresh start building my app.
As I look over those 80 hours, I see that 24 of them were spent tracking down and solving just one problem; recording on the physical iPhone refused to work. I pursued a lot of dead-end leads trying to track this one down. Ultimately, I ended up debugging the iOS specific code used to implement one of the libraries I had included in my project.
I think Flutter does a beautiful job of creating a UI abstraction that works well in both Android and iOS. Also, the growing library of packages to accomplish other things (such as audio playback and recording in my case) nicely abstracts these functions so that you don’t have to deal with the nuances of each underlying operating system… until you do.
“All non-trivial abstractions,
to some degree, are leaky.”
– Joel Spolsky
Joel Spolsky popularized the Law of Leaky Abstractions which every developer before and since has had to wrestle with to get anything useful done. And this one bit me good. The problem I spent 24 hours working on over a week and a half is written up in detail here. The solution turned out to be rather simple. But getting to the actual cause of the problem required digging beneath that nice abstraction to figure out what was going on in Objective C with the iOS implementation. And further complicating the situation for me, the problem never manifested in the simulator!
In the end, having understood the problem in enough detail, I was able to fork the repository for the stereo audio playback library that was causing interference and make a code change that eliminated the problem altogether. The author of the library quickly accepted my changes so I was happy to have contributed to a more reliable ecosystem of Flutter packages even if it cost me the equivalent of 3 full days.
Many more leaks in the Flutter abstraction layer will be found as new packages are created and as people start to use them. And this will definitely take some time. For those who decide to build their app now in Flutter, participation in abstraction leak finding and patching is almost necessarily a requirement.
Given the relative young age of Flutter, I’m actually surprised that I was able to create this app without writing any platform specific code (with the exception of fixing the stereo library of course). I’m very happy with app performance and responsiveness as well as the flexibility I had in fulfilling my design intentions. Flutter will be a leading candidate when I’m ready to create the next app I need – along with the expectation of handling a few leaky abstractions.