As you probably know from my previous articles and talks, I love Flutter. I really think that this is one of the best options to develop a high-quality multi-platform app. It’s so easy to create custom views with complex animations – especially, comparing with the native Android View layer. The way it promotes composition over inheritance and immutable widgets, or encourages you to make the view a pure function of a state makes me happy.
Unfortunately, I cannot say the same about Dart, the language that Flutter is written in, and that you’re supposed to use to develop Flutter apps. Why? There are at least 13 reasons for that.
#1 No null safety
Yes, I know that this will hopefully become
obsolete
in the (near?) future, but for now, we have to deal with it. Anyway, for the language with the first release in the year 2018 (I mean Dart 2 of course), it’s a shame not to have null safety from the very beginning.
#2 No data classes
Data classes are classes whose main purpose is to hold data [sic!]. They are highly useful as Value Object from DDD, and also, with proper language support, it’s very convenient to use immutable data classes for functional programming style.
By proper support I mean something like this in Kotlin:
Even without implementing copy (and equality) functionality the Dart way is more verbose:
And it still lacks some features that are provided by Kotlin. E.g. in Kotlin you can pass these parameters as positional or as named (and even mix them:
val user = User('John', age = 30)
), while in Dart you’re
either
making them positional
or
named – but in that case they will be optional and default to
null
.
There is a nice package
built_value
that solves the problem with deep equality check and generating copy method, and we use it a lot in our codebase, but it’s not an ideal solution.
First, it still requires some boilerplate code compared to Kotlin version:
Second, it lacks some features, e.g. you cannot easily enforce required parameters during compile time.
Will we have proper support for data classes in Dart?
Probably
, but not anytime soon.
#3 No sealed classes
Another feature in Kotlin language that we use a lot: sealed classes, aka co-product types, aka sum types, aka “enums on steroids”… They are extremely useful if you want to make your types sound.
Essentially, they represent a restricted class hierarchy: a value can have one of the types from a limited set. Unlike enum, this value is not a singleton, but a proper class that can have multiple instances with different states.
Why is it useful? Let’s see at probably the most common example:
This is a nice alternative to exceptions: instead of catching exceptions (or sometimes
forgetting
to do that) you’re forced to deal with the result, which can be
either
error
or
valid result (the corresponding type in many functional languages and libraries is called exactly like this:
Either
). Not only you will have to take a possible error into account, but Kotlin will provide some nice features as well. Do you see casting inside
when
branches? You don’t, because no manual casting is needed. Kotlin is smart enough to do
smart casting
(pun intended) automatically.
What about Dart? Well, maybe
someday
we will get there.
#4 No custom values in enums
Sometimes (well, actually quite often) you need to associate some value with enum. In Kotlin it’s as easy as this:
Dart?
Looks like no
. You can use extensions to achieve similar behavior:
But first, it’s more verbose, and second, that leads us to the next point…
#5 Compiler is not smart enough
The previous example gives me this warning:
This function has a return type of ‘int’, but doesn’t end with a return statement. Try adding a return statement, or changing the return type to ‘void’.
What?
Level
enum has only 3 possible options, and they are all listed. There’s no way for this function to return
void
. I don’t want to add
default
branch here (otherwise, if I add another option to the enum, I can forget to update the extension, and it will silently return an incorrect result). I don’t want to suppress the warning for the same reason. I want an error here, if and only if I didn’t match all the possible options: that’s what Kotlin does.
#6 No singletons
How can you define singleton in Kotlin? Like this:
object Singleton
(I won’t even create gist for that). Anything similar in Dart? I guess this is the simplest way:
Not a big deal, of course, but when you have to write this for
every
case it adds up. And we use them a lot in our Kotlin project e.g. as “constant” branches of sealed classes… Oh wait, we don’t have sealed classes in Dart anyway.
#7 No switch/if/try expressions
Do you remember the example from
#3 No sealed classes
? There’s no return keyword, yet the function is returning a string. That’s possible thanks to two things:
fun getAnswer(): String = "42"
is equivalent to
fun getAnswer(): String { return "42" }
. In Dart you can do it as well with
String getAnswer() => '42';
.
when
is an expression: it means you can return the result of
when
and the compiler is smart enough to infer the correct type since every branch is returning
String
.
And it’s not only about concise syntax. With returning
when
compiler forces us to provide all the possible options (e.g. in case of enums or sealed class you must either specify all the options or use
else
branch).
#8 No protected keyword
In order to make method (or variable) private, you need to prefix its name with underscore. For making it protected you can use
@protected
annotation from the
meta
package. In the first case, you’ll get a compilation error when trying to access it from outside of the allowed scope. In the second case, the best you can get is static analyzer feedback. Dart doesn’t have language support for protected members so it’s all or nothing. No compromises.
#9 No type aliases
Actually, type aliases in Dart exist, but only for function types. So you can write something like
typedef FormatDate = String Function(DateTime);
, but not
typedef Json = Map
<
String, dynamic
>
. So, if you’re working with json in your project, add this type to snippets, you will use it
often
.
Yeah, the sun
will shine
on our side of the fence. Someday…
#10 No concise syntax
Ok, you can say here: “You just got used to Kotlin syntax, and try to turn Dart into Kotlin”. Well, that’s true, I find Kotlin syntax pretty nice, but it isn’t just about getting used to it. Things like lambda syntax are so much more concise in Kotlin, e.g.
listOf(1, 2, 3).map { it.toString() }
looks better than
[1,2,3].map((i) => i.toString());
even in such a simple example. When you have a multiline lambda (or chains of lambdas) Dart way becomes too complex for writing and reading. And this required semicolon… Come on, it’s the year 2020!
#11 No nested classes/extensions
What about having some way to nest classes? E.g. we would like to do something like this with messages:
Dart? Sure, we have a
feature request
for that.
#12 No proper generic variance
In Dart type variables in generic classes are covariant. Why is it bad? Because it’s an easy way to shoot yourself in the foot. Let’s take a look at this example:
This will compile without any problems, but in the runtime, you will receive an error:
TypeError: Instance of 'Dog': type 'Dog' is not a subtype of type 'Cat'
.
You can try to do the same trick in Kotlin:
But it won’t even compile. It will give you an error:
Type inference failed. Expected type mismatch: inferred type is Animal but Cat was expected
.
Here
you can read about generics variance in Kotlin, and
this
is an ongoing discussion about variance in Dart.
#13 No final classes
Effective Java says: “Design and document for inheritance or else prohibit it”. So in Java it’s a good practice to use final classes as often as possible. Kotlin takes one step further and makes classes final by default. What about Dart? Well, there’s no way at all to make a class final. You cannot prohibit inheritance. Freedom for everyone!
So, are there only negative things I can say about Dart? Well, there are actually some points that I really like:
Classes are interfaces. Each class implicitly defines an interface that consists of its public members. So you can just
implement
any class and provide overridden functionality: nice thing to use in tests for mocking implementation.
No type erasure. Unlike Kotlin,
List<String>
is still
List<String>
in runtime.
Last but definitely not least: language is evolving. We’ve got extensions, probably we will get null safety soon enough. Hopefully, other things from my list will become obsolete someday and Dart will eventually become
Kotlin
a modern and safe language.
Analytical cookies help us improve our website by collecting and reporting information on how you use it.
The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
At Mews we use cookies and other technology to provide you with our services and for functional, analytical and advertising purposes. Please, read our
Cookie Policy
for more information or
manage your preferences
.
If you decline, your information won’t be tracked when you visit this website. A single cookie will be used in your browser to remember your preference not to be tracked.