A Kotlin developer's view on Flutter/Dart tooling (part 1)
Tags: flutter, kotlin, dart, tooling
Published: 2020-05-08 10:37:30

*Small thing in advance: Comparing Flutter to native development was quite a hot topic (pun intended), and still is. I'm not looking for a 'winner' here. I think there are pros and cons on both sides. Often it is good to look over the fence and see what other languages, tools or ecosystems have to offer. And only by pointing out the flaws and by learning how others do it better we can improve things in our own beloved world. So let's improve!*

As you might have guessed from my other blogposts, I'm a huge fan of [Kotlin](https://kotlinlang.org/). For me, this began when I first used Kotlin around 3 years ago. Back then, my coworker and friend [Niklas](https://twitter.com/Niklas_L) and I built a backend in 100% Kotlin. It was one of the most pleasant development experiences I've ever had. Since then, I've built a bunch of apps and other software with it, and was very pleased with the language and the tooling.

Fast-forward to today, me and an awesome team are developing a [Flutter](https://flutter.dev/) app for a customer since end of last year. And in Flutter, as you might already know, you'll write almost no Kotlin code (only if you need to have some native functionality). Almost the entire codebase is written in [Dart](https://dart.dev/). And having spent some time with Flutter & Dart, I gathered quite a few examples were the tools and the language were lacking and left me in the rain, in a way I didn't expect.

## Think you really like something? *Or: what a deprivation test is*

To explain to you how I felt I better explain what a deprivation test is. FastCompany has an article on how GitHub [uses deprivation testing for product design](https://www.fastcompany.com/3010972/how-github-uses-deprivation-testing-to-hone-product-design). The basic idea is: Someone has a thing. That could be a product, feature or more. Now you give that person a new thing, and let that person use that for some time. Now comes the deprivation part: You take that new thing away from that person. So the person now is back using the old thing. And then you observe their emotions and you collect feedback. Was it ok for them to go back? Or are they missing the new thing - and why?

I'm sure everyone of you had multiple moments of that in the past. Maybe you had a smartphone that broke, and you had to use an older feature phone for some time, only to be infuriated by it. Or you already used and loved Git, and were forced to use Subversion again because a project didn't migrate yet. Or you had to go back to your old coffee beans because your go-to coffee beans were out of stock, only to find out you hadn't notice how much you liked your go-to coffe beans already.

To me, I had a very strong deprivation moment when going back to a Java project after developing in Kotlin. I couldn't really tell it before, but it made me love Kotlin *even more* - because I discovered that the vast amount of small improvements were making my coding experience so much better than I expected. I couldn't even tell it before what was making me so much happier - but I could tell very easily when those things disappeared.

And to get back to the topic, I also had a strong deprivation moment when switching from Kotlin to Flutter/Dart. Which I didn't expect, because I actually used Dart (without Flutter) before starting with Kotlin to learn about and implement an [interpreter](https://github.com/mreichelt/monkey-dart). Coming from Java I thought Dart was quite nice. But coming from Kotlin, my perspective changed completely. What was I missing? Without further ado: Let's dive in!

## Dart, coming from Kotlin

### Missing null safety

The first thing hits hard. For a long time (and still), Dart had no null safety like Kotlin built-in. So you'll find having to write a lot of these (Dart):

```dart
  Person({@required this.name, @required this.age})
      : assert(name != null),
        assert(age != null);
```

Instead of this (Kotlin):

```kotlin
class Person(val name: String, val age: Int)
```

And your IDE won't show an error if you write something like `Person(name: null, age: null)` in Dart. Brace yourselves for NullPointerExceptions.

I can't wait for the Dart team to finalize the support for null safety (they are [working on it since 1.5 years](https://github.com/dart-lang/language/issues/110)), and hope the Dart/Flutter ecosystem - including all the packages and plugins - catches up quickly. If someone of the Dart teams reads this: I think you are doing an amazing job - take your time to finish this feature! 👏 I really hope we'll be able to use it soon.

### No data classes / sealed classes

Yep, no data classes in Dart built-in. The class above was actually written like this (Dart):

```dart
class Person {
  final String name;
  final int age;

  Person({@required this.name, @required this.age})
      : assert(name != null),
        assert(age != null);
  
  // toString, equals, hashCode not shown for brevity

}
```

In Kotlin, the example above was only missing the `data` keyword:

```kotlin
data class Person(val name: String, val age: Int)
```

There are tricks to get something like this in Dart by using generators, but come on - it's 2020, and even [Java 14 has records now](https://aboullaite.me/java-14-records/).

I want to highlight a talk by [Pascal Welsch](https://twitter.com/passsy) named [Making Dart more like Kotlin](https://www.youtube.com/watch?v=AW3c3-LwT3Q), in which he highlights a pretty new package [freezed](https://pub.dev/packages/freezed) by Remi Rousselet. This generates code so you can get something like data classes - and even sealed classes - in Dart. It looks promising, but I didn't get my hands on it yet. Still, I believe these features must come to the Dart language as a standard.

### Enums

Enums in Dart can't have any fields. So this is basically what you can do with enums:

```dart
enum Environment { prod, test }
```

Want to add a backend URL to all those? Write a switch/case. Want to add a lambda that initializes something for them? Write a switch/case. In Kotlin (as in Java) you can just add them as constructor parameters:

```kotlin
enum class Environment(val url : String) {
    prod("https://prod"),
    test("https://test")
}
```

### Semicolons;

Semicolons in Dart are everywhere! This is something that I really wouldn't have assumed I would notice, but after spending a lot of time in Kotlin, it actually is noticable, and having no semicolons would make the code more readable.

### _privateFields

Dart has no `private` keyword. Private fields are written with underscore prefix. I really can't stand this, this tiny thing spreads over the whole codebase…

```dart
final _message = 'please release me';

void doSomething() {
    why(_message);
    println('is ${this._message}');
    final bar = _message + _message + _message + ' so hard';
}
```

### The new tabs vs. spaces: 'single quotes' vs. "double quotes"

Yep, you can write strings either way:

```dart
String a = '$foo $bar';
String b = "$foo $bar";
```

It's basically the same. And I can't even imagine how many commits there must be out there that change single quotes to double quotes, or the other one around. I mean, developers like to argue about small things anyway. Would be boring otherwise, wouldn't it? 😛

### if / switch can not be used as expressions

This is a really useful feature in Kotlin, and I didn't think I would miss it. Boy, was I wrong. Let's see a cool example from Kotlin, where we use the combined power of sealed classes, smart casts, and the return value of `when`:

```kotlin
val result = createUser(credentials)
val message = when (result) {
    is Success -> "User ${result.user.username} created"
    is UserDoesAlreadyExist -> "Error: user ${result.username} does already exist"
}
```

Yes, `when` can be treated as an expression - and the Kotlin compiler will show you an error if you forget to handle a possible branch (less bugs!).

### Only constant values for optional parameters

I noticed this when I tried to make a lambda parameter non-nullable in Dart, like this:

```dart
void doSomething({Function something = (){}})
```

This is the error you'll see:

![Default value of an optional parameter must be constant](https://dev-to-uploads.s3.amazonaws.com/i/edvkzbpm2dvcaylhmfu9.png)

I tripped a few times over this (not only for lambdas), and it's kind of sad this doesn't work.

### Constructors

One thing that already hit me a few times in Dart is that `final` fields can not be set in the constructor body. Let's see this simple example, where I have two classes `ServiceA` and `ServiceB`, where the second depends on the first, and I want to initialize them in my class:

```dart
class MyClass {
  final ServiceA serviceA;
  final ServiceB serviceB;

  MyClass(){
    this.serviceA = ServiceA();
    this.serviceB = ServiceB(serviceA);
  };
}
```

![serviceA can't be used as a setter because it's final](https://dev-to-uploads.s3.amazonaws.com/i/y1sgb5nu54wlwl2xl224.png)

To do that we would have to implement a series of constructors instead:

```dart
class MyClass {
  final ServiceA serviceA;
  final ServiceB serviceB;

  MyClass(this.serviceA, this.serviceB);

  MyClass.foo(this.serviceA) : serviceB = ServiceB(serviceA);

  MyClass.bar() : this.foo(ServiceA());
}
```

As you can see, this grows quite a bit and makes the code unreadable. And we only had *one* field that depended on the other. Imagine having 3 or more dependent fields. Yikes.

### await: Easy to forget

Let's see this code:

```dart
Future<void> doSomethingImportant() async {
  // something important here
}

Future<void> main() async {
  doSomethingImportant();
}
```

In the default [Dart Lint configuration](https://dart.dev/guides/language/analysis-options), it'll just let you do this - and depending on how your code executes, the important stuff will never run. Because you forgot to add an `await` keyword in front of the `doSomethingImportant()` call. And the IDE didn't warn you. There is a Lint option called [unawaited_futures](https://dart-lang.github.io/linter/lints/unawaited_futures.html) from the [pedantic](https://pub.dev/packages/pedantic) package that will prevent you from this. But that option really should be the default.

Kotlin, with Coroutines, will prevent you from accidentally forgetting this:

```kotlin
suspend fun doSomethingImportant() {
    // something important here
}

fun main() {
    doSomethingImportant();
}
```

The IDE shows an error directly, and you must either make that method `suspend` as well or wrap it in something like `runBlocking`:

![Kotlin shows error because suspending call is called without Coroutine](https://dev-to-uploads.s3.amazonaws.com/i/l7w2cc7ltrjlxejv4r99.png)

Oh, and by the way: You can wrap suspending functions in `runBlocking` in Kotlin. You can't in Dart. The only possible option is to slap `await` and `async` up to the top-most caller, which might be the `main` method - forcing you to add those keywords in every function of that stack, sometimes forcing you to entirely re-do your architecture if you missed this. Sometimes, programming languages have to be pragmatic to not stand in the way.

### Collection API is bad

Lists in Dart are mutable by default. If you're used to Kotlin, you'll know how many nice collection functions there are. Most of them are missing in Dart. And in order to have immutable collections, you'll have to write your own. And did I mention that lists don't have equality defined?

```dart
final a = [1, 2, 3];
final b = [1, 2, 3];

void main() {
  // prints false
  print(a == b);
  
  badMethod(a);
  // a has now changed - good luck in finding those bugs introduced by mutable collections
  print(a);
}

void badMethod(List<int> a) {
  print(a);
  a.add(4); // please don't do this
}
```

Of course Dart devs will say that you can use `const` instead of `final` to get immutable values. And indeed, if we would write `const a` we would get an exception when we call `a.add(4)`:

```plain
Uncaught Error: Unsupported operation: add
```

But the point of Kotlin's immutable collections is that you didn't even have an `add` method to call in the first place.

Again, there are nice libraries out there that deal with this. But this is something that belongs to the language itself - otherwise projects will never settle on a common language.

Here is an example how a nice method like `associateBy` (which does exist in the `dartx` package, but does not exist in Dart) could help every Dart developer to easily create a map from a list:

```dart
Map<int, Person> associateById(List<Person> people) {
  return people.associateBy((person) => person.id);
}
```

If you use Kotlin, you'll get used to use these functions all the time.

### So many missing stdlib functions

If you come from Kotlin, there are *so many* extension functions that make your life easier. Just look at all those extension functions [defined in String.kt](https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/text/Strings.kt). There are `removePrefix`, `removeSurrounding`, `substringBefore`, just to name a few. All those together are a massive improvement in developer productivity.

Again, Dart developers noticed this - and finally that we have extension functions in Dart, we can use those to make code much better. Some Dart developers built the great [dartx](https://github.com/leisim/dartx) package, which adds all sort of functions to the standard types. And again, this should be something that *every Dart developer* should be able to rely on in every project - without setting anything up.

### Questionable default implementations

If you ever handled files and directories in Dart, you'll see that `Directory` and `File` have no equality defined. If you ever tried to create a set of these: nope, you'll have to convert them to absolute String paths instead in order for the set to work.

Another example is the use of `dynamic` in APIs. Have a look at the [stdout](https://api.flutter.dev/flutter/dart-io/ProcessResult/stdout.html) of the [ProcessResult](https://api.flutter.dev/flutter/dart-io/ProcessResult-class.html) class. It's dynamic. According to the documentation:

> Standard output from the process. The value used for the stdoutEncoding argument to Process.run determines the type. If null was used this value is of type List\<int\> otherwise it is of type String.

So we have to just cast to `String` and hope it works. Type-safety is important: please don't use `dynamic` if you can avoid it.

## More

Wow, that was already a bunch. And I didn't even run out of things I wanted to write about.

If you are a Flutter or Dart developer, and you stumbled over at least a few of these things: you're not alone! I feel your pain. Maybe you found even more things I didn't mention: Please [write me on Twitter](https://twitter.com/mreichelt). I want to collect more of these, so we have a good overview what we as a community can improve in the future.

As I started this blog post I had in mind to write about the shortcomings of Dart as well as Flutter tooling. I realized there is so much I found with Dart - I didn't even get to the Flutter bit yet. That's why there will be a **part 2** of this post soon! [Follow me on Twitter](https://twitter.com/mreichelt) and click the `+FOLLOW` button below to get the new post while it's hot!

*Cover photo by [Christopher Burns](https://unsplash.com/@christopher__burns) on [Unsplash](https://unsplash.com/photos/Wiu3w-99tNg).*