Freezed — A powerful way to generate data classes in Flutter

Dev with Ahmad
7 min readJul 22, 2023

Creating data classes in flutter sometimes requires you to do multiple of repetitive boring steps. However, using packages like Freezed will change this for you completely 🙌

Freezed is a code-generation package that helps you to generate data classes in Dart easily with only adding annotations to your classes. It prevents you from writing hundreds of error-prone lines again and again.

Sometimes you just want a class that holds some data and nothing else. Freezed makes it easy to create such classes with minimal boilerplate and maximum flexibility.

In this article, I will show you how to use Freezed package in Flutter and how to handle different scenarios with it 🧐

What is a data class?

To begin, we need to define what is a data class to fully understand the need of this package.

A data class is a class that only contains data and no logic. It is usually used to represent the state of an object or a response from an API. A data class should have the following properties:

  • It should be immutable, meaning that once created, it cannot be modified.
  • It should have a constructor that takes all the fields as parameters.
  • It should override the == operator and the hashCode method to compare objects by value instead of by reference.
  • It should support copying with modified fields, also known as copyWith.

Why use Freezed?

As we talked in the beginning of this article, that writing data classes by hand can be tedious and error-prone. You have to write a lot of boilerplate code for each class, and if you change something, you have to update all the places where you use it.

Also, if you want to handle different cases or variants of a data class, such as success, failure, loading, etc., you have to write even more code using enums or sealed classes.

Freezed solves these problems by generating all the boilerplate code for you. You just have to write a simple annotation and a few lines of code, and Freezed will take care of the rest. Freezed also supports union types, which allow you to define multiple variants of a data class with different fields and behaviors.

How to use Freezed?

To use Freezed in your Flutter project, you need to add the following dependencies to your pubspec.yaml file:

Then, you need to run flutter pub get to install them.

Next, you need to create your .dart data classes. For example, let’s create a file called user.dart where we will define a data class for a user registered in our system.

In this file, we need to import the following packages:

The first two imports are required for using Freezed annotations and @required keyword. The last two “part” are for the generated code that Freezed will create for us.

Now, we can define our data class using the @freezed annotation and the with _$ClassName mixin. For example:

Here, we have defined a data class called User with three fields: id, name, and email. We have also defined a factory constructor that takes all the fields as parameters and assigns them to the corresponding properties. We have used the @required keyword to indicate that these fields are mandatory and cannot be null.

We have also defined another factory constructor that takes a JSON map as an argument and converts it into a User object using the generated code from Freezed. This is useful for parsing data from an API or a local storage.

Finally, we have used the with _$User mixin to enable some features from Freezed, such as copyWith, toString, etc.

Now, we need to run the following command in the terminal to generate the code for our data class:

This will create two files: user.freezed.dart and user.g.dart. The first one contains the code for union types, which we will see later. The second one contains the code for JSON serialization, equality, hashCode, toString, and copyWith.

We can now use our data class in our code. For example, we can create a User object like this:

Since we defined a named constructor in the beginning, we now need to start with the field’s name then assign a value to it.

We can also access the fields of the object like this

One good thing is that now we can compare two objects by comparing the fields values by using == operator

Sometimes we want to copy an object completely and just change one value (e.g. name). Using .copyWith we can create a copy of the object with the ability to change one field’s value only.

When dealing with an external APIs it is useful to have an easy way to deal with JSON objects (from and to json functionality). Freezed will generate the required functions for us just by annotating the class.

toJson function will convert your data class into a Map object

We can also convert a JSON Map to a data object using the fromJson constructor generated by Freezed:

How to use union types?

Union types are a feature of Freezed that allow you to define multiple variants of a data class with different fields and behaviors. For example, let’s say we want to define a data class for the state of a network request. We can have three possible states: loading, success, and failure.

To define a union type, we need to use the @freezed annotation and the with _$ClassName mixin as before. However, instead of using a single factory constructor, we need to use multiple factory constructors with different names and parameters. For example:

Here, we have defined a union type called RequestState with three variants: Loading, Success, and Failure. Each variant has its own factory constructor with its own parameters. The Loading variant has no parameters, the Success variant has a User parameter, and the Failure variant has a String parameter.

We can now create objects of different variants like this:

We can also access the fields of each variant like this:

However, we cannot access the fields of other variants that are not present in the current object. For example, we cannot do this:

To handle different variants of a union type, we need to use the when or maybeWhen methods. These methods take a function for each variant and execute the corresponding function based on the current object. For example:

The when method requires that we provide a function for every variant. If we don’t want to handle some variants, we can use the maybeWhen method instead. This method takes an optional parameter called orElse, which is a function that is executed if none of the other functions match.

Defining variants for your classes will be so helpful when using BLoC state management in your application. One way to think if this is that you will have a different variant for each state in your BLoC ( e.g. Initial, Loading, Success, Error ) and based on the variant you can display a specific UI to represent that state.

Conclusion

In this article, we have learned how to use Freezed package in Flutter to create data classes and union types. We have seen how Freezed can generate all the boilerplate code for us and make our code more concise, readable, and maintainable. We have also learned how to handle different scenarios with union types using the when and maybeWhen methods.

Freezed is a powerful and useful package that can simplify our data modeling and state management in Flutter. If you want to learn more about Freezed, you can check out its documentation and GitHub repository.

I hope you enjoyed this article and found it helpful. If you have any questions or feedback, feel free to leave a comment below 👋

--

--