Flutter — the Good, the Bad and the Ugly (Part 1)
In this mini-series, you will get the perspective of an experienced native iOS developer who has spent almost one-year building and releasing a Flutter app. Of course, the presented topics are purely subjective — your experience may vary.
When the team decided to switch to Flutter, no one had much experience writing code in Dart or used Flutter widgets to compose UI. The following insight was gained during the development from a novice to becoming a somewhat experienced Flutter developer.
Spoiler alert: using Flutter is a successful environment for us as we use it to continue building our mobile app. But where there is light, also a shadow! This series is about both.
If you’re interested in seeing what we have built? Check it out with this link.
In this part, I’m talking about the good parts. Look out for parts two and three about the bad and ugly of developing Flutter applications.
The Good
Widgets and the Widget Tree
The idea of everything being a widget keeps concepts simple and easy to understand. Organizing UI, navigation, and state all as widgets is an almost perfect approach.
What makes the concept of the widget tree so powerful is the ability to build complex UI out of many tiny widgets. While being able to compose and rearrange them without a hassle, they continue to be clear and maintainable.
In practice, it worked out very well. During a short ramp-up phase where we ended up in extremely large single widgets (containing all the UI of a whole screen), we figured out how to build UI composed of small widgets.
State handling
The flutter community came up with some brilliant ways of handling state for your UI. You can use stateful widgets, inherited widgets, BLoC, and many other approaches. Most of them implement a reactive approach, eliminating the stale UI problem that often introduces bugs and strange behavior into mobile apps.
When starting with Flutter, choosing the right approach for your app can be overwhelming. In the end, we settled on Riverpod since it allowed us to separate UI from business logic nicely. It also let us apply some best practices from the MVVM pattern we use in native development. (If you are interested in the details: a view model does more or less reassemble the state of a state notifier here.)
However, the state management pattern you should use depends a lot on the needs of your app. And having a choice is a good thing!
Learning curve
We only spent about two weeks of research and learning before we started the actual development of our app. Getting into the basics and learning Dart as well as knowing the most common widgets can be achieved in very little time. That is especially true compared to learning Swift and UIKit or Kotlin and Android.
As an example, take your lists or recycler /table views. Compare the linked examples with the following code snipped:
class ListExample extends StatelessWidget {
static final content = ['Apple', 'Banana', 'Kiwi', 'Mango'];@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: content.length,
itemBuilder: (_, index) => Text(content[index]),
);
}
}
Almost Native UI
With a bit of effort, you can produce nearly native-looking UIs for both Android and iOS. (I can’t talk about how Flutter on the web or desktop is working, since I have no experience there.)
Of course there is some effort involved in getting native-looking widgets on both platforms. Our approach here is to build widgets that, depending on the platform, apply different values or return a completely different widget (sub-) tree. That makes it possible to combine those into a platform-agnostic widget that we use to compose the final UI.
If you look close enough, I’m sure you can always spot some tiny things that would look or work differently on native apps. Nevertheless, a regular user should not experience any negative impact from using a Flutter app in comparison to a native one.
This was part one of the mini-series on Flutter. I hope you enjoyed it! You can continue reading in part two.