In this mini-series, you will get the perspective of an experienced native iOS developer on spending almost one-year building and releasing a Flutter app. Of course, the presented topics are purely subjective — your experience might vary.

When the team decided to switch to Flutter, no one on our team had much experience writing code in Dart or using Flutter widgets to compose UI. So the following insight was gained during the development from a novice to becoming a somewhat experienced Flutter developer.

To already take away the conclusion: using Flutter is a success for us as we are continuing to build our mobile app using it. But where is light is also shadow. This series is about both.

If you now got interested in seeing what we have built? Follow this link.

In this part, I’m talking about the bad parts. Look out for parts one and three about the good and ugly parts of developing Flutter applications.

The Bad


As mentioned, Dart is easy to learn. But here, it already stops for me on the positive side. When coming from Swift, many things are annoying or make it easy to introduce bugs. Like missing automatic generated setters and getters for properties, missing default constructors with arguments, required parentheses for if and other statements. I could come up with a lot more points, but here will follow two more noteworthy points:

Often the type inference regarding generics is very limited. You have to write down the types of closures manually. It becomes apparent when using RxDart.

final a = Stream.value(0);
final b = Stream.value(0);
final c = Rx.combineLatest2(a, b, (a, b) => a + b);

Looking at the snippet, the compiler should be fine inferring all types here. We should get c to be of type Stream<int>. But no! a and b inside the closure are of type Object? and you have to add the correct generic parameters to the combineLatest call to make this snippet work.

Another issue with generics, well actually more the type system of Dart is that there is also no way to reference Self or This, limiting the usefulness of the type system in many complex settings. We already ran multiple times into the problem of having to resort to some other solution or writing less type-safe code by just casting values.

Limited Usefulness of Extensions
Extensions are great. You can add functionality to existing types — even if those types are provided by some external library.

Adding instance methods to classes works as expected. Consider the following code snipped:

class A {

int a;
extension Adder on A {
void add(int value) => a += value;

With the Adder extension, you can now call add on any instance of A. This is nice, but more advanced uses of extensions are not supported: Adding (factory) constructs to classes using extensions is not allowed (something I use all the time with Swift). For a workaround, you might do something like this:

extension Constructor on A {
// factory Adder.default() => A(0); <- not supported!
static A fallback() => A(0);

But now, instead of calling A.fallback() you need to call Constructor.fallback(). Defying the whole idea of adding functionality to classes using extensions. You could have just created a free function or auxiliary class for generating your custom constructor/factory.

Strange gaps

Back from Dart to Flutter, which offers tons of widgets to choose and customise in order to build your UI. Sometimes it is just hard to find the right widgets to do the job. But often, you will find functionality is just not supported out of the box. Here are two examples:

Grouped Lists
Often, you want to display a list of things grouped by some property. For example, show a list of people sorted in alphabetical order, having the first letter as a group header. A very common pattern you might think: not with Flutter.

The standard ListView widget shipping with Flutter does not support groups or group headers. There is, of course, some third-party library that adds this functionality. But still, I often wonder why this is not part of the standard Flutter widgets.

Button States for iOS
The same goes for CupertinoButton, where it is easy to define enabled and disabled colors. But adjusting the pressed state color is not so easy. Flutter assumes you will always want to make your button half-transparent when pressed. There is also no out-of-the-box way to change the button content for the disabled state (like using a different foreground color).
The Flutter team did not put so much love into their Cupertino buttons as they did for the Material counterparts. Since with ButtonStyle, you have a lot more control over buttons on the Android side.

This was part two of the mini-series on Flutter. I hope you enjoyed it! You can continue reading in part three.