Flutter — Freezed

Idan Ayalon
6 min readJun 23, 2022

Hi, my name is Idan Ayalon, the founder of BlueBirdCoders. I decided to release an article about Flutter every few days to help developers better understand the Flutter world. This article will focus on the freezed package. I will assume that you heard about It so that I won’t dive into all the small details, but I’ll give you an excellent foundation to start using it, which is essential. Just a tiny thing, if you are against code generation, this lib might not be for you, So have fun with writing models manually, writing copyWith, toString, ==, and not using the power of unions/sealed classes for your state.

When I started to use Flutter, I missed Kotlin’s features like sealed classes and the keyword when. Also, data classes were a blast to use because they saved much time writing all the boilerplate. Well, in the Flutter worlds, these features don’t exist in the dart, so the creator of freezed tried to bring all the stuff to us, and they did a good job. I can’t say it’s perfect, but ill show you some use-cases that will teach you how to use it like a pro.

Quick setup before we begin

dart pub add freezed
dart pub add freezed_annotation

And then to the dev_dependencies add:

dart pub add build_runner --dev
dart pub add freezed_annotation --dev
dart pub add json_serializable --dev

To use freezed and other generating libs, you are going to use this command (notice that the watch flag will automatically generate for you when you change the models)

flutter pub run build_runner watch --delete-conflicting-outputs

You might also want to disable all the warnings that will happen on your generated files from lint in your analysis_options.yaml

analyzer
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
errors:
invalid_annotation_target: ignore:

The Syntex

The syntax of freezed is not super friendly, with the power of vs. code and practice getting used to it worth it. Let’s use an example of a Motorcycle class.

The first thing is to import freezed and annotate your class with @freezed like so

Then let’s add brand and year, string and int.

Most people’s reaction will be, “Yak! what the hell is that?”. And I understand. That was my reaction when I used this library for the first time. But because it saves me so much time, I can forgive. Stuff like copyWith, toString, == to help us check if two objects are equal and, of course, handle toJson and fromJson, we need to specify the generated file’s name. What we have here:

@freezed = on every freezed class

Using mixin with the name of the class and prefix of _$

Using a private constructor

Declare your properties in a factory constructor. Notice that I added a default value. It’s not a must. Just remember that freezed will not like if you have a property that is not nullable or has a @default value.

Notice — If you need to use fromJson/toJson because it’s a response model, you can declare only the fromJson. The library will auto-generate the toJson for you.

Your freezed class is ready and pumped with all the goodies (named parameters, copyWith, toString, equal (==), fromJson, toJson, default values, assertions). A quick example with our motorcycle class:

final gs = Motorcycle(brand: 'BMW GS1200', year: 2019);

Congrats! You have a new bike and didn’t spend 20k on it. Now let’s say you want to change the motorcycle brand. With copyWith, it’s simple.

final newerGs = gs.copyWith(year: 2021);

copyWith will only change the year and return a new motorcycle. Because we are generating toString printing, this object will give you.

Motorcycle(brand: 'BMW GS1200', year: 2021); // print

Checking for == will check the absolute values.

Motorcycle(brand: 'BMW', year: 2021) == Motorcycle(brand: 'BMW', year: 2021) // true

Now let’s imagine a nested freezed object inside a motorcycle called Country, with only one property “name”). we deep copy we can change the internal thing.

Motorcycle newMotor = gs.copyWith(
country: newMotor.country.copyWith(
name: 'Israel'),
);

This is a simple example, but it can support more nested structures.

toJson/fromJson

With freezed to handle serialization, you need to implement only the fromJson (like in the photo above), and you can use vscode extension to do it for you.

If the property name is the same as in the JSON, you don’t have to do anything. Let’s use our motorcycle example again. If your JSON looks like this, you don’t need to do anything. It will automatically generate everything for you.

{
"brand" : "BMW",
"year" : 2019
}

But if your JSON has a snake_case key in it.

{
"brand" : "BMW",
"year" : 2019,
"created_at" {timestamp},
}

Then you don’t want a snake case in your dart file, and you will have to tell freezed what the JsonKey is.

Well, most of the friendly backend developers will give us snake_case. Now I should write for all of my properties this boilerplate? And what if I’m also using the Hive database with its annotation? I’ll get a horrid class that is not readable even to the machine. Is there a better way to use JsonKeys? Yes.

To avoid writing JsonKey all over the place, you can use a package called to build and configure your policy for JSON files. I’m not will dive into that but follow the following steps:

  1. Add build.yaml file into your project’s root, check this for more info

2. Now, you can remove all @JsonKeys from your models and write them in camelCase, so this

@JsonKey(name: 'created_at') final int? createdAt

can be this while our backend sends us { “create_at”: … } as key.

final int? createdAt

Awesome!

Unions/Sealed

The last thing and super important is the Unions/Sealed class. As an example, we will use an authentication use case. So your app inits and needs to decide what page to show based on your authentication state. For this example, ill use MobX as the state management. So for this example, we have two possible conditions.

Authenticated
UnAuthenticated

Let’s first create a Union class that represents those states

Like before, we need to add the @freezed annotation at the top of the course and add ‘part’ with the original name of the file and the .freezed.dart. Notice that for this class, we don’t use toJson and fromJson because there’s no serialization happening.

After running the state class, let’s generate the code with this command.

flutter pub run build_runner watch --delete-conflicting-outputs

Now let’s watch the magic! A freezed file will be generated ‘auth_state.freezed.dart’, and inside we will have methods like when, whenMaybe that we will see in a second.

Next, let’s create a MobX store with an observable variable representing the AuthState. (You can use whatever state management you want, it doesn’t matter).

(In MobX, it’s good to keep the observables private and expose only the computed variables).

OK, now we can bind the state object to the UI. I’ll create a widget that will listen to the state variable. Notice the nice syntax that the when gives us.

I added support also a splash to AuthState. And in the second, I said it because we are using it when all the parameters are required, so I had to implement it. If not all params are needed, let’s say only splash and authenticated, so use the maybeWhen method instead, but then you should implement orElse if you want to select only one of the params whenOrNull. Also, you can use the map or maybeMap.

Let’s wrap up. I hope that you enjoyed this article and learned something new.

About me, I’ve been a Flutter developer since late 2019. I founded BlueBirdCoders to create unique Flutter apps, and you are welcome to contact me anytime.

Also, I founded “Flutter Israel,” a fantastic Flutter community on Facebook. You are welcome to join and get all the best articles and support for Flutter.

--

--