Freelancing for Pale Blue

Looking for flexible work opportunities that fit your schedule?


Immutable collections and objects for Flutter and Dart

Flutter Oct 22, 2020

Immutability is a known good principle of software engineering. Whatever doesn't need to change after it's created, it shouldn't. You get a lot of goodies with this approach, among others, fewer bugs because something is changed accidentally and thread-safety for "free".

So it's kind of sad that Dart (and by extension Flutter) does not have built-in support for immutable objects and collections.

That's not a big issue though because built_collection and built_value packages exist. With 2 additional lines in your dependency list, you get immutable collections and immutable objects respectively.

I will provide a super concise overview of the usage of both packages. Check out the respective documentation for additional examples and features.

Set up

Add these to your pubspec.yaml (check built_collection and built_value for the latest versions).

dependencies:
  built_collection: ^4.3.2
  built_value: ^7.1.0

Immutable collections

Offering BuiltList, BuiltSet, BuiltMap which are the SDK equivalents but once they are created cannot change (mutate). Use the convenient extension methods to create them from a mutable equivalent.

import 'package:built_collection/built_collection.dart';

final builtList = [1, 2, 3].build();
final builtSet = {1, 2, 3}.build();
final builtMap = {1: 'one', 2: 'two', 3: 'three'}.build();

To modify an immutable collection, call toBuilder() to get a mutable version. When you are done with your changes, call build() again to make it immutable.

import 'package:built_collection/built_collection.dart';

final listBuilder = builtList.toBuilder();
listBuilder.addAll([4, 5, 6]);
final newBuiltList = listBuilder.build();

Note that build_collection does not implement the mutable equivalent interfaces. For instance, a method accepting List<Int> won't be able to accept a BuiltList<Int>. In case you want to accept both, the common ancestor is Iterable<Int>.

Another goodie of these collections is that they are comparable and hashable out-of-the-box (in contrast to their mutable counterparts). Finally, null is not allowed in any of these collections.

Immutable objects

These are for creating data value classes (similar to Kotlin's data classes). It's using  code generation, so you would need to include the generated file in your imports using the part statement. The boilerplate is a bit verbose, but it gets the job done.

import 'package:built_value/built_value.dart';

part 'my_data.g.dart';

abstract class MyData
    implements Built<MyData, MyDataBuilder> {

  String get name;
  
  int get age;
  
  factory MyData(String name, int age) => _$MyData._(name: name, age: age);
  
  MyData._();
}
my_data.dart

For more complex / bigger objects, you can just provide the builder interface directly. Note that by default, setting a value to null is not allowed except if explicitly annotating the field appropriately. Finally, you can convert an immutable built_value to a mutable form by calling toBuilder().

import 'package:built_value/built_value.dart';

part 'my_data.g.dart';

abstract class MyOtherData implements Built<MyOtherData, MyOtherDataBuilder> {

  String get name;
  
  int get age;
  
  @nullable
  String get comment;

  factory MyOtherData([void Function(MyOtherDataBuilder) updates]) =
      _$MyOtherData;
      
  MyOtherData._();
}

[...]

// No need to set the mutable field, it will be set to null by default
final nullIfNotSet = MyOtherData((b) => b
  ..name = 'Tom'
  ..age = 23);

// Convert the immutable, to mutable, and back to immutable
final mutable = nullIfNotSet.toBuilder();
builder.comment = 'Hello world';
final immutable = builder.build();

// Auto-generated equality, hashCode and toString.
assert(nullIfNotSet != immutable);
my_other_data.dart

By now, you have a grasp of how to use immutable collections and objects in your Flutter apps. Bear in mind that this only scratches the surface of these packages (e.g. buit_value offers serializers/deserializers). Check out their respective docs for more.

Happy coding!

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.