An iOS purist thrown into Flutter
Things are weird over here guys.
My first thought when I moved from developing iOS apps in Swift over to Flutter was how different things were. I was used to
UIView.animate, Lottie (still usable, but better options are available for Flutter), and the lack of semicolons. Flutter has been cool for cross deployment, rapid development, and shareable code, but it hasn’t been easy to get here.
The Beginning Days
When I first joined the team at Gathr, the UI code was visibly written by backend engineers. It had the tinge of “it works, ship it” that all frontend engineers are all-too-familiar with (hats off to Tarun Thummala for even getting it that far). It took a lot of time as the sole frontend engineer and designer to get things in a better state. It took even longer to get the widgets and screens into order because I myself was so new to the concept of Flutter and had no idea what any of the best practices were for the dart language and framework.
Stateful versus Stateless? Keys? BuildContext?
The most familiarity with Flutter I had when coming over was lifecycle events like
initState which looked a lot like
viewDidLoad:. Keys were a whole different beast that took a while to understand and use effectively. I started with a lot of:
“Oh, it’s not reloading? Throw a
Spoiler: Generally, do not do that
Medium, YouTube, and trying things out was super helpful when working through these problems. I would 100% suggest reading through articles for Flutter and then trying things out yourself to get a feel for how the code should work and be written.
When moving between languages and frameworks, it’s only natural to want to do things in the ways that you’re used to. On the other hand though, there are sometimes ideas or frameworks that can improve the experience for both the developers and users. The next few sections are examples of what I believe are good carryovers from Swift and UIKit:
When I was working over in the iOS side of the world, I would make microframeworks for different features, theme management, etc. to increase reusability within our codebase. This has been one of the bigger initiatives that I have taken onto myself for our team.
We’ve begun to use Melos to manage sub-packages within a monorepo structure. This provides the fastest and most efficient way for us to develop the packages that we use in the app in tandem with the packages themselves. Here are some examples of what we’ve used this pattern for:
Our Design System
Our design system at Gathr, the Venn System (article incoming) is what we base our application off of. We’ve been through a few iterations of Venn internally (before it was named), but each time has been a huge improvement, in both design and code. Most recently, we have fully migrated all of the themeing logic and core components into a package to handle and share the code across projects.
Another key area for us. We started with just Firebase Analytics, but as we grew and needed to push data to different places such as Amplitude, we needed a more generic solution. Using our micropackage infrastructure, we were able to pull out the analytics code and have it be managed outside of the primary application.
Dart becoming null-safe was a blessing. Swift being very safe with first class optionals was a dream come true for me. Dart’s null safety is not as clean but the I forced the team to properly adopt optionals and understand them at a deeper level. Because UI is so tied in with the nullable objects, this really helped us to create a better contract between the frontend and backend.
What Works Well
Flutter has a lot going for it and things that I appreciate a lot versus my prior experience with pure iOS development.
Unless you bring in 3rd party libraries, state management is not simple in iOS land. Flutter provides
StatefulWidget which lets me easily update the state and trigger a rebuild. This was a somewhat new concept to me as you don’t really do that in UIKit, but it was a well received feature of the framework.
Hot Reload to the Rescue
Back in my iOS days, we were pre-SwiftUI, so to render one change in the app you had to fully rebuild. In big projects, this could take minutes just to see the color slightly change or a 2pt shift in an AutoLayout constraint. Hot reloading and restarting has been awesome in Flutter since it lets me instantly see the changes that I made in my widget. Sometimes It doesn’t reload correctly and you need to hot restart or rebuild, but even then this feature has vastly increased my development throughput on the frontend.
With one set of code, we can deploy to iOS and Android. With Flutter 3.0, we’re beginning to look at desktop and web deployment as well, which is really awesome. There are also only a few hiccups that we have run into when deploying across platforms, and they have been fairly easily solved, albeit sometimes annoying.
So far it has probably sounded like Flutter is the next best thing since sliced bread, but it’s not all rainbows and candy over here. I’ve found some quirks that we have had to work through.
Not all frameworks are created equally, and my time in Flutter has made that very much apparent. Many bigger companies opt to wrap their iOS and Android native SDKs and deliver the functionality down into the Flutter side via the
PlatformChannel interface. While this works, it doesn’t always create the best experience for the developers nor the users.
What happens when you need to use a framework (e.g. Bitmoji) and there isn’t already a version for it on pub.dev? You have to do it yourself.
In an extremely fast-paced startup like at Gathr, we have to weigh if it’s worth it to put in the time to write the interfaces between the platforms that we use, use a different service, or pause the feature altogether depending on the development time.
I get a lot of flashbacks to the early days of Swift with Dart. There are things that are missing in the language that would make life easier. Semicolons are a drag. I could (and may) write a whole article on my thoughts and wishes on Dart.
From the Swift side, the biggest things that I miss are
if-let, and Swift’s implementation of named parameters.
Dart isn’t all bad though. It provides a type-safe land (looking at you, React 👀) of async/await niceness for our app’s backend code. There is an active community around both the development of the language and Flutter itself, which makes it a much better place than I had initially thought.
When you download an app on the App Store, it’s fairly easy to tell what’s native and what is a cross-platform app. React is one of the worst offenders of “Oh, this is React Native”. Flutter is somewhat better in that it at least implements Google’s Material as a baseline, which helps it to feel less foreign, but there is still a lot of T.L.C. that needs to go into a Flutter app to make it feel like it belongs on a user’s phone and that it isn’t a toy. We’re definitely getting there with Gathr and I’m excited to continue to share our experiences!
Flutter is not SwiftUI
Dart is not Swift
Both of these things are okay. Flutter excels in cross-platform development and we could not get to where we are today without it. Sure I miss things about Swift, SwiftUI, and UIKit, but we’re moving too fast to yearn for tools of the past ;)