How to use frozen

by SkillAiNest

The flutter is a UI tool cut that Google has developed. It has gained immense popularity from the same code base for the ability to create beautiful and locally compiled applications for mobile, web, and desktop.

While the dart, the language behind the flurry, is powerful, writing the data models often involve repeated and mistaken tasks. A normal model may be required:

  • A converter and a description of features

  • Over -riding toStringFor, for, for,. == Operator, and hashCode

  • Implement A. copyWith Method

  • Writing serialization (toJson) And dyserialization (fromJson) Ways

Doing all this by hand can swift your code faster and reduce the ability to read.

This is the place where frozen. Frozen is a Dart Code generator that develops a boiler plate for unintentional data classes, unions, patterns, cloning, and JSON serialization. With frozen, you can write a comprehensive and secure model while the package handles repeated parts.

In this tutorial, you will learn how to use freezing data classes, creating JSON serialization, and securing a variety of powerful unions to handle multiple states. Finally, you will find a way to reduce the boiler plate and make it easier to clean and maintain your blowing code.

The table of content

Provisions

Before starting, you should be comfortable:

  1. Fluttering: Be able to create a new fluttering project and run it on an emulator or device.

  2. The basic principles of the dart language: Understand how classes, constructors and methods work.

  3. Command line tools: Like being able to run a command flutter pub get Or flutter pub run.

  4. Json concepts: Know what Json is and how it is commonly used for API data exchange.

If you are already pleased with these titles, you are ready to sink in frozen.

Why frozen?

When fluttering, two challenges arise when working with the data model: Instability And Serialization. Frozen helps to solve both clearly and automatically.

1. Istability

In the dart, the items are variable as default. This means that once you make an item, its fields can be changed anywhere in your code. Although simple, it can produce neutral side effects, such as mistakenly editing the user object in a part of your app and breaking the logic elsewhere.

Manually requires a lot of boiler plate to ensure instability: you must announce all fields finalEnforce copyWith Ways to make edited copies, and correctly override == And hashCode To maintain the equation of the object. It can be repeated and mistaken.

How does frozen help:

Frozen automatically produces irreversible classes. All fields are finalAnd a copyWith The method has been provided so that you can safely create edited copies without changing the original item. Pls, frozen handles == And hashCode Your thing, which makes sure that your items are used properly when you are used in comparison or collecting. This reduces the boiler plate rapidly, implementing instability.

2. Serialization

When interacting with APIS, changing dart items in JSON and it is a common task. Without automation, you have to write toJson And fromJson Ways to each class, carefully map each field. It is easy to repeatedly and wrong, especially when your models change over time.

How does frozen help:

Is integrated with frozen json_serializable Package to automatically create serialization and deserralization logic. You just interpret your class and run the code generator, and then the frozen is created fully from working toJson And fromJson Ways for you. This not only saves time but also reduces the chances of mistakes and keeps your code clean and maintained.

Without frozen: a manual instance

What is a basic here User Class looks like frozen:

class User {
  final String name;
  final int age;
  final String email;

  const User({
    required this.name,
    required this.age,
    required this.email,
  });

  User copyWith({
    String? name,
    int? age,
    String? email,
  }) {
    return User(
      name: name ?? this.name,
      age: age ?? this.age,
      email: email ?? this.email,
    );
  }

  Map<String, dynamic> toJson() {
    return {
      'name': name,
      'age': age,
      'email': email,
    };
  }

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      name: json('name') as String,
      age: json('age') as int,
      email: json('email') as String,
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is User &&
          runtimeType == other.runtimeType &&
          name == other.name &&
          age == other.age &&
          email == other.email;

  @override
  int get hashCode => name.hashCode ^ age.hashCode ^ email.hashCode;

  @override
  String toString() {
    return 'User{name: $name, age: $age, email: $email}';
  }
}

This is verb, and it is easy to lose details like refreshing hashCode When adding new fields.

With frozen: a clean replacement

Now when you consider these challenges a frozen solution, let’s see how it makes it easy and clean to work with the data model. In this section, you will install the necessary packages, set up a frozen class, and create a boiler plate code. Once this setup is completed, we will dive into examples in which it shows how to use the frozen class, including items and JSON serialization.

First, install frozen and related packages. Add it to your own Pub Spring.mal File:

dependencies:
  freezed_annotation: ^2.4.1
  json_annotation: ^4.8.1

dev_dependencies:
  flutter_lints: ^2.0.0
  build_runner: ^2.0.0
  freezed: ^2.4.7
  json_serializable: ^6.7.1

Then run away:

flutter pub get

For pure dart projects, use:

dart pub get

Frozen class descriptions

Create a file that has a name user.dart And add the following:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';

@freezed
class User with _$User {
  factory User({required String name, required int age}) = _User;
}

What is happening in this code is here:

  • import 'package:freezed_annotation/freezed_annotation.dart';: Desired interpretations by frozen.

  • part 'user.freezed.dart';: It indicates that the frozen will produce the code in this file.

  • @freezed: The following class tells to be frozen to take action.

  • class User with _$User: Declares User Class with _$User Part connects the class with a code created.

  • factory User({required String name, required int age}) = _User;: A factory conductor explains. Being frozen creates processing classes (_User) Behind the scenes.

Code Generation is running

Run the following command to prepare the code:

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

For Dart Plans:

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

It creates user.freezed.dart File, which is like a boiler plate copyWithFor, for, for,. ==For, for, for,. hashCodeAnd toString.

Using a frozen class

Let’s see the frozen in action:

void main() {
  final user = User(name: 'John Doe', age: 25);
  final user2 = user.copyWith(name: 'Jane Doe');
  final user3 = user2;

  print(user);
  print(user2);
  print(user2 == user3);
  print('Name: ${user.name}');
  print('Age: ${user.age}');
}

What is happening here:

  • final user = User(name: 'John Doe', age: 25);: Creates a new unmanned User.

  • final user2 = user.copyWith(name: 'Jane Doe');: Creates a copy of user With a new name but maintains the same age.

  • final user3 = user2;: Points user3 As a single item user2.

  • print(user);: Thanks to the born, shows the wire of reading toString.

  • print(user2 == user3);: Compares the objects produced ==.

Adding json serialization

Refusal user.dart To support JSON:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'user.freezed.dart';
part 'user.g.dart';

@freezed
class User with _$User {
  factory User({required String name, required int age}) = _User;

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

In the new parts of the code:

Next, run the generator once again:

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

Using JSON serialization

Examples use:

void main() {
  final userJson = {'name': 'Alice', 'age': 30};
  final user = User.fromJson(userJson);

  print('Name: ${user.name}');
  print('Age: ${user.age}');

  final userBackToJson = user.toJson();
  print('Back to JSON: $userBackToJson');
}

In this code:

Advanced use: Frozen unions

So far, we have used frozen for unintentional data models. Is another powerful feature of frozen Unions (Also known as sealed classes).

Unions allow you to represent multiple potential states of an item in a type of safe way. This is especially useful in the flurry when it works with unprecedented tasks such as API calls, where you often have states. loadingFor, for, for,. successAnd error.

Union’s explanation

Create a new file that says result.dart:

import 'package:freezed_annotation/freezed_annotation.dart';

part 'result.freezed.dart';

@freezed
class Result<T> with _$Result<T> {
  const factory Result.loading() = Loading;
  const factory Result.success(T data) = Success;
  const factory Result.error(String message) = Error;
}

Line Byline Code Specification:

  • import 'package:freezed_annotation/freezed_annotation.dart';: Imports the interpretation library required to freeze.

  • part 'result.freezed.dart';: This file tells to freeze the boiler plate to produce plate.

  • @freezedFrozen guidance to prepare a code for the interpretation class.

  • class Result with _$Result: Announces normal class Result Which type of data can be kept T.

  • const factory Result.loading() = Loading;: Describes loading State Loading Is the class born.

  • const factory Result.success(T data) = Success;: Describes success State with relevant data.

  • const factory Result.error(String message) = Error;: Describes error State with a message.

After savings, prepare the code:

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

Using the union

Let’s use our use of an API call and return results Result Union:

FutureString>> fetchUserData() async {
  await Future.delayed(const Duration(seconds: 2)); 

  final success = true; 

  if (success) {
    return const Result.success("User data fetched successfully");
  } else {
    return const Result.error("Failed to fetch user data");
  }
}

What is happening here:

  • Future> fetchUserData(): Loots A Result The thing that contains String Data

  • await Future.delayed(...): A real network call calls the call, a delay of 2 seconds.

  • if (success) { ... } else { ... }: Rule either A success Or error Conclusion

The pattern matching with frozen

A great part of the frozen .He is Pattern matching. You can handle all the states without long writing if Check

void main() async {
  final result = await fetchUserData();

  result.when(
    loading: () => print("Loading..."),
    success: (data) => print("Success: $data"),
    error: (message) => print("Error: $message"),
  );
}

What is happening in this code is here:

This ensures that all states have been handled. If you forget someone, the composition will show a mistake.

Mibon: Deliver from partial states

maybeWhen Have a secure and more flexible version when. While when You need to handle All possible statesFor, for, for,. maybeWhen Just lets you handle the people you care about and gives the Fallback with it orElse.

When you are not interested in every state, but only one sub -set.

Sometimes you just care about some states. This is how you can use maybeWhen:

result.maybeWhen(
  success: (data) => print("Data received: $data"),
  orElse: () => print("No data"),
);

What is happening here:

  • success: (data) Only run when the current state is success.

  • orElse Works as a flockback for all other states (loadingFor, for, for,. errorEtc.).

So in this piece, the code is showing how you can only react to the state of success while safely ignoring everything.

Map: Working directly with state items

Have another point of view mapWhich provides a full -class example:

result.map(
  loading: (value) => print("Currently loading"),
  success: (value) => print("Got success: ${value.data}"),
  error: (value) => print("Got error: ${value.message}"),
);

Here, each branch receives the created class (LoadingFor, for, for,. SuccessFor, for, for,. Error), Providing you with access to all fields.

Why use unions?

Unions shine when they make apps with irreversible logic. For example:

  • Network requests: loadingFor, for, for,. successFor, for, for,. error

  • Verification of verification: validFor, for, for,. invalidFor, for, for,. submitting

  • Confirmation: authenticatedFor, for, for,. unauthenticatedFor, for, for,. loading

Instead of writing bool isLoading And String? error Flags scattered in your app, unions provide you with a systematic, type -secure way for the model state.

Conclusion

Frozen filter is an essential tool for developers that want to reduce the boiler plate while maintaining a safe, undeniable and easily serializable model.

By handling the repeated code copyWithChecking equality, and JSon serialization, frozen, allows you to focus on building applications instead of writing a boiler plate.

Whether you are early or experienced developer, frozen and improve your code base reading, safety and maintaining.

For advanced features and best methods, go to official frozen documents Pub.dev.

You may also like

Leave a Comment

At Skillainest, we believe the future belongs to those who embrace AI, upgrade their skills, and stay ahead of the curve.

Get latest news

Subscribe my Newsletter for new blog posts, tips & new photos. Let's stay updated!

@2025 Skillainest.Designed and Developed by Pro