Porting a 75,000 line native iOS app to Flutter
Lines of Code & Development Speed
When I embarked on the port I estimated it would take 6 man months. Well, the project is coming in ahead of schedule! For me, that’s fairly unusual.?
How come?
In Flutter, instead of using Storyboards (iOS) or layouts in XML (Android) you create an app’s user interface entirely in code by compositing widgets in a widget tree. That sounded a bit scary to me but I jumped in and although it took some time to get used to, before long I was wielding widget trees with some dexterity.
And then, about three months into the port, a curious thing started happening. As I became more proficient and was porting functionality more quickly, the number of lines of code in the project began increasing less quickly. This was strange because I was porting a fair amount of business logic that had an almost one to one ratio in terms of lines of code.
What was happening was that I was able to create classes and write functions to reuse the UI portions of the code much more easily than in native iOS. Often, I could make my UI widgets reusable by simply refactoring them to use a couple of extra parameters and bingo, task done. If that didn’t work I could often simply wrap another widget around an existing widget to accomplish the desired behaviour. It was very satisfying!
In the final release I expect the line count to be less than 30,000 (compared to 75,000 for the native iOS version). Of course, the native iOS version contains code that was developed along the way but ended up being superseded or not being used. I estimate unused code to account for 15,000 lines. In other words 60,000 lines needed to be ported.
So, to summarise, the Flutter port will have half the lines of code of the native iOS original!
Additionally, the Flutter project doesn’t contain any Storyboard XML. And there’s a lot of XML in those Storyboards. The native iOS project contained:
- 15 Storyboards
- 47 nib files
- 92 View Controllers
After struggling with Storyboards for years, trying to follow best practices, building UIs in Flutter is liberating and fast.
I hadn’t realised what a huge proportion of my time was spent writing UI related code and futzing around with storyboards and auto layout constraints. I’ve heard many times, and tacitly agreed, that separating the UI layout from your code is desirable, but that hasn’t been my experience with Flutter. And it’s not just native iOS best practices that promote this separation of concerns concept with regard to the UI. I remember wrestling with XML in Microsoft WPF and see this Quora question Why does Android use XML to define user UI and not just Java code?
Storyboards are a top down way of composing a layout (good for desktop applications) while widgets take a bottom up approach which is a big simplification when you’re building a mobile app. I’ve read that this approach is similar to the way screens are laid out in React Native or with CSS flex-boxes. This article What’s Revolutionary about Flutter by Wm Leler in Hackernoon does a great job of explaining this stuff.
Composing the UI out of widgets was helped a great deal by:
- Flutter’s stateful “Hot Reload” feature which, most of the time, is able to incorporate code changes seamlessly into your running app within a couple of seconds.
- An Android Studio shortcut Alt + Enter to insert or remove UI widgets (rows, columns, containers). See Tips for using Android Studio to develop Flutter apps. I assume there’s something similar in VS Code. This seems trivial but I can’t emphasise enough how useful this shortcut is. With Hot Reload and Alt + Enter I can whip up a screen in minutes or whimsically experiment with a UI.
- Setting debugPaintSizeEnabled to true. This displays brightly coloured borders around all the UI widgets. I thought I’d need this more than I did but it’s invaluable when a layout problem does occur.