In this article, we are going to explore the freezed and working mechanism in the flutter application. With the help of freezed, we can reduce the lines of code.

Freezed is a code-generation package that helps you to create data classes in Dart. It stops us from writing a ton of error-prone lines. Sometimes we just want a class that obtains its values in a constructor, a toString method, and maybe value impartiality. That lone is a lot. But now visualize you want objects to remain fixed. For that, we need an extra copyWith method. Freezed is a robust and scalable code generator for data classes. It’s reliable yet comes with a demerit: it’s really hard for beginners to find the right blend and the necessary features to use for their app logic. So freezed is one of the packages which is used to generate data class and union class. Also, it can be used to serialize and deserialize the JSON data. Rémi Rousselet created Freezed to be a code generator for immutable classes,

Before

class Datum {
  Datum({
    required this.id,
    required this.email,
    required this.firstName,
    required this.lastName,
    required this.avatar,
  });

  final int id;
  final String email;
  final String firstName;
  final String lastName;
  final String avatar;

  factory Datum.fromJson(Map<String, dynamic> json) => Datum(
        id: json["id"],
        email: json["email"],
        firstName: json["first_name"],
        lastName: json["last_name"],
        avatar: json["avatar"],
      );
}

After

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'data_model.freezed.dart';
part 'data_model.g.dart';

@freezed
class DataModel with _$DataModel {
  const factory DataModel({
    required int id,
    required String name,
    required String email,
    required String avatar,
  }) = _DataModel;

  factory DataModel.fromJson(Map<String, dynamic> json) =>
      _$DataModelFromJson(json);
}

We compare model classes with and without freezed. A model class generation without freezed means the “boilerplate” code isn’t useful in complex applications with a lot of functionality. A model class generation without freezed has a lot of code, and this approach doesn’t scale. Using the package, we can eliminate a lot of boilerplate code.

How to Use Freezed:

First, we have to add some dependencies in our project by which we can easily use Freezed. Build_Runner, Freezed, Freezed_Annotation.

dev_dependencies:
  flutter_test:
    sdk: flutter
  build_runner: ^2.1.7
  freezed: ^1.1.1
  freezed_annotation: ^1.1.0
  json_serializable: ^4.1.3

or we can add these dependencies by using commands.

flutter pub add freezed_annotation
flutter pub add --dev build_runner
flutter pub add --dev freezed
flutter pub add --dev json_serializable
  • freezed:- Freezed package is used to create the model. It’s code generator for data classes/unions/pattern-matching/cloning.
  • build_runner:- The build_runner package provides a concrete way of generating files using Dart code, outside of tools like the pub. Unlike pub serve/build, files are always generated directly on disk, and rebuilds are incremental – inspired by tools such as Bazel.
  • freezed_annotation:- Freezed_annotations for freezed. This package does nothing without freezed. If we want to use Freezed so we have to use this package without this we can’t use Freezed. Json_serializable uses annotations to define its operations.
  • json_serializable: Annotating Dart classes automatically generates code for converting to and from JSON. The JsonSerializable annotation will generate code for a class to/from JSON.
  • Run the generator:- To run the code generator, execute the following command.
flutter pub run build_runner build

When the packages providing Builder are configured with a build. ymal file they are designed to be consumed using a generated build script. Most builders should need little or no configuration, see the documentation provided with the Builder to decide whether the build needs to be customized. If it does we may also provide a build. ymal with the configuration.

Creating a Model using Freezed:- First, we have to create a class that has the @freezed annotation. We create a class with the name of DataModel{}.

import 'package:freezed_annotation/freezed_annotation.dart';
part 'data.freezed.dart';
part 'data.g.dart';
@freezed
class DataModel{}

After creating the class we define the class with mixins. A mixin is a class whose methods and properties can be used by other classes without sub-classing.

import 'package:freezed_annotation/freezed_annotation.dart';
part 'data.freezed.dart';
part 'data.g.dart';
@freezed
class DataModelwith _$DataModel{}

Now we add a factory method as a constructor with a list of all the arguments/properties. Here we have defined @Default inside the factory method which comes from ‘freezed_annotation’.The @Default annotation is used to set a default value for non-required properties.

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'data_model.freezed.dart';
part 'data_model.g.dart';

@freezed
class DataModel with _$DataModel {
  const factory DataModel({
    required int id,
    required String name,
    required String email,
    required String avatar,
  }) = _DataModel;
}

And at last, we have now created our constructor, In which we have to implement fromJson/toJson. In the freezed package, fromJson and toJson are not generated, but it knows what json_serializable is.

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'data_model.freezed.dart';
part 'data_model.g.dart';

@freezed
class DataModel with _$DataModel {
  const factory DataModel({
    required int id,
    required String name,
    required String email,
    required String avatar,
  }) = _DataModel;

  factory DataModel.fromJson(Map<String, dynamic> json) =>
      _$DataModelFromJson(json);
}

For creating a data.freezed.dart and data.g.dart we have to run the command if we don’t run this command so our code will show errors. The next step is to navigate within the terminal to the folder where our project is located and execute the following command.

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

The following snippet defines a model named DataModel:

  • DataModel has 4 properties: idname, email and avatar
  • Because we are using @freezed, all of this class’s properties are immutable.
  • Since we defined a fromJson, this class is de/serializable. Freezed will add a toJson method for us.
  • Freezed will also automatically generate:
    • copyWith method, for cloning the object with different properties
    • toString override listing all the properties of the object
    • an operator == and hashCode override (since Person is immutable)

From this example, we can notice a few things:

  • It is necessary to annotate our model with @freezed (or @Freezed/@unfreezed, more about that later).
    This annotation is what tells Freezed to generate code for that class.
  • We must also apply a mixin with the name of our class, prefixed by _$. This mixin is what defines the various properties/methods of our object.
  • When defining a constructor in a Freezed class, we should use the factory keyword as showcased (const is optional).
    The parameters of this constructor will be the list of all properties that this class contains.
    Parameters don’t have to be named and required. Feel free to use positional optional parameters if you want!

Defining a mutable class instead of an immutable one

So far, we’ve seen how to define a model where all of its properties are final; but you may want to define mutable properties in your model.

Freezed supports this, by replacing the @freezed annotation with @unfreezed:

@unfreezed
class Person with _$Person {
  factory Person({
    required String fName,
    required String lName,
    required final int age,
  }) = _Person;

  factory Person.fromJson(Map<String, Object?> json)
      => _$PersonFromJson(json);
}

This defines a model mostly identical to our previous snippets, but with the following differences:

  • fName and lName are now mutable. As such, we can write:
void main() {
  var person = Person(fName: 'Sagar', lName: 'Koju', age: 23);

  person.firstName = 'Sa';
  person.lastName = 'gar';
}
  • age is still immutable, because we explicitly marked the property as final.
  • Person no-longer has a custom ==/hashCode implementation:
void main() {
  var john = Person(fName: 'John', lName: 'Smith', age: 42);
  var john2 = Person(fName: 'John', lName: 'Smith', age: 42);

  print(john == john2); // false
}
  • Of course, since our Person class is mutable, it is no longer possible to instantiate it using const.

Using the “copyWith” method

The method returns an updated instance with the new properties. The state is updated when its value changes in state management solutions. State management solutions update the state when its value is changed. Here’s when copyWith comes in handy.

For example, if we define:

@freezed
class Person with _$Person {
  factory Person(String name, int? age) = _Person;
}

Then we could write:

void main() {
  var person = Person('Remi', 24);

  // `age` not passed, its value is preserved
  print(person.copyWith(name: 'Dash')); // Person(name: Dash, age: 24)
  // `age` is set to `null`
  print(person.copyWith(age: null)); // Person(name: Remi, age: null)
}

In this article, we have been through how to use Freezed in Flutter. In this, we can see how it is to use, how much code will be saved, and also the time we can save by using freezed By using we can perform many operations and create a Flutter application.