Flutter: Dart Immutable Objects and Values

Marco Muccinelli
Level Up Coding
Published in
4 min readJun 11, 2020

--

When I started developing Flutter apps, I also met the Dart language for the first time in my life. I was quite surprised to notice that the Flutter team chose a language that does not support immutability extensively out-of-the-box. I still think that’s quite strange, in particular if you compare it to Swift UI that is deeply rooted in value types.

Let’s explore our alternatives.

The naïve immutability

First of all, let’s see what Dart (at current version, which is 2.8) provides out-of-the-box.

  • @immutable annotation says that every field inside Person (and its subclasses) must be final. Otherwise, Dart compiler will throw a warning, but not an error.
  • final keyword says that you cannot assign a value to the property after its initialization. Otherwise, Dart compiler will emit an error.
  • UnmodifiableListView is a List wrapper that forbids modifications (e.g.: add or remove items). It exposes methods like add() or addAll(), but they throw exceptions at runtime.

It’s just ok, but it completely misses compiler-time safety requirement and you don’t have any method you would expect from a data class (like equality, hashing and cloning).

Source generation

Google itself proposes a package to help: it’s called built_value. You define a class description and a source generator creates the missing parts in a paired file.

The same idea is borrowed by freezed, another Dart package that uses a more modern syntax to achieve similar results. You install it by inserting a dependency inside pubspec.yaml file:

I’m using any version since this package does not expose a functionality that may change during time, so it’s ok to remain always at latest release.

Then, you can open a terminal window and you can execute:

build_runner will observe the filesystem and it will generate the proper code every time you save a definition.

Basic usage

Let’s convert Person to freezed:

  • The first line says to build_runner that the paired file is person.freezed.dart. This name could not be changed.
  • @freezed annotation tells that the following data class declaration have to be synthesized by the library.
  • The factory initializer specifies also the properties declared by this value type.
  • Please note that freezed is ready to manage nullable types that will be introduced in the upcoming Dart 2.9 release. At this time it checks for null values only at runtime. In this case only jobTitle property is nullable.
  • If you need to provide default value for non-required properties, you have to use @Default annotation.

And what do you get for free?

  • toString is overridden to provide a pretty description of the object to print.
  • == operator compares the objects property by property.

Make copies

copyWith method returns a new instance with updated properties:

If you have nested hierarchies to copy, you could use the straightforward chained syntax:

So p2 will contain a copy of p1 with new best friend’s father name and surname, if p1 has a best friend.

Custom methods and properties

If you need to add custom methods, you cannot use the simplified mixin syntax anymore:

You can also add custom getters, even annotated with @late, which is equivalent to Swift’s lazy or Kotlin’s lateinit:

`late` will be soon an official Dart keyword.

Sealed classes

Powerful enumerations are loved in Swift and Kotlin world and I miss them very much. With freezed you can write beauties like this:

Obviously you cannot perform pattern matching with switch but there are methods like when or maybeWhen that freezed synthesized for you:

Immutable collections

What about compile-time safety of collections? freezed does nothing to mitigate this problem, while built_value has a twin package called build_collection that exposes immutable lists, dictionaries and sets. These ones conform to Iterable so they are pretty compatible with official Dart collections.

But I’d like to dare a little bit more with kt_dart package, a port of kotlin-stdlib including immutable collections and other benefits, like deep comparison of lists and map/filter/reduce without exotic names. You install it by inserting a new dependency in pubspec.yaml:

You can modify Person data class just changing types:

The friends list is now immutable by default. The creation of the list is easier than before:

For example, if you want to create a method to generate a new immutable person with a new friend, it’s easy like that:

Just a note: since KtList does not conform to Iterable, you cannot use for loop directly, but you should take the iterator before:

VS Code Integration

Since the boilerplate code could be tedious and hard to remember, maybe it’s convenient to setup some user snippets by selecting Preferences: Configure User Snippets in VS Code launched and then selecting Dart language. Now you can copy and paste the following snippets:

You can also exclude the generated files from Explorer tab. To do so, open Settings and search for Files: Exclude preference. Then, you can add this pattern to the list: **/*.freezed.dart.

JSON serialization and deserialization

freezed is integrated by design with json_serializable package but there are some not obvious challenging using kt_dart: I discussed this topic in depth in this follow-up.

--

--