Flutter and Freezed: A Beginner's Guide

Flutter and Freezed: A Beginner's Guide

Beginner's guide to understanding Freezed package in Flutter

Flutter, a UI toolkit developed by Google, has gained immense popularity for its ability to create beautiful and natively compiled applications for mobile, web, and desktop from a single codebase.

Dart is undoubtedly impressive, yet the process of defining a "model" can become cumbersome. This involves:

  1. Defining a constructor along with its properties.

  2. Overriding methods such as toString, operator ==, and hashCode.

  3. Implementing a copyWith method for object cloning.

  4. Managing de/serialization tasks.

Completing all these steps often results in lengthy and error-prone code, negatively impacting the overall readability of your model.

Freezed tries to fix that by implementing most of this for you, allowing you to focus on the definition of your model. It's a code generator for data-classes/unions/pattern-matching/cloning.

What is Freezed?

Freezed is a Dart package that facilitates the generation of boilerplate code for immutable classes and JSON serialization/deserialization. It helps developers write concise and readable code by automatically generating the repetitive parts. With Freezed, you can create data classes with minimal effort, allowing you to focus on your application's logic rather than dealing with mundane tasks.

Why Freezed?

Freezed addresses the challenges associated with immutability and serialization in Dart and Flutter. Immutability ensures that objects cannot be modified once created, leading to safer and more predictable code. Serialization, on the other hand, is crucial for converting Dart objects into JSON and vice versa, a common requirement when dealing with APIs.

By using Freezed, you can:

  1. Improve Code Readability: Freezed generates boilerplate code for you, reducing the amount of code you need to write and maintain.

  2. Immutability: Freezed ensures that your objects are immutable, preventing unintended changes and enhancing the predictability of your code.

  3. JSON Serialization: With Freezed, you can effortlessly generate code for converting your Dart objects to and from JSON, simplifying API communication.

Without Freezed:

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}';
  }
}

With Freezed:

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'freezed_class.freezed.dart';
part 'freezed_class.g.dart';


@freezed
abstract class User with _$User {
  const factory User(String name, int age, String email) = _User;

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

Getting Started with Freezed

Before diving into implementation, ensure you have the necessary dependencies by adding the following lines to your pubspec.yaml 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

After updating your pubspec.yaml, run the following commands in your terminal:

flutter pub get

or for a Dart project:

dart pub get

Implementation

Let's create a simple Flutter project and explore Freezed's capabilities.

flutter create my_freezed_project
cd my_freezed_project

Defining a Freezed Class

Now, let's create a simple data class using Freezed:

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;
}

In this example, we define a User class with two fields: name and age. The @freezed annotation, along with the part 'user.freezed.dart'; line, instructs Freezed to generate the necessary code.

Running Code Generation

To generate the code, run the following command:

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

or for a Dart project:

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

This command triggers the code generation process, creating the file user.freezed.dart with the generated code.

Using the Freezed Class

Now, let's utilize the generated class in our Flutter application:

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

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

This demonstrates how Freezed simplifies the process of creating immutable data classes and makes the code more readable.

JSON Serialization

Freezed also supports automatic JSON serialization. Let's enhance our User class for 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);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

By adding the _$UserFromJson and _$UserToJson methods, we enable Freezed to automatically generate code for converting our objects to and from JSON.

Now, let's use JSON serialization with our User class:

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

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

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

This showcases how Freezed simplifies the process of integrating JSON serialization into your Flutter application.

Freezed is a valuable tool in the Flutter developer's toolbox, offering a seamless solution for immutability and JSON serialization. By automating code generation, Freezed allows developers to focus on building robust and maintainable applications. Whether you're a beginner or an experienced Flutter developer, incorporating Freezed into your projects can significantly enhance your workflow and code quality.

For more in-depth information, examples, and updates on the Freezed package, it's highly recommended to explore the official documentation available on the Dart package repository: Freezed Package onpub.dev.

The official documentation provides comprehensive details on various aspects of Freezed, including advanced features, best practices, and any recent updates. Additionally, you can find examples, use cases, and detailed explanations that will aid in understanding and effectively implementing Freezed in your Flutter projects.

Did you find this article valuable?

Support Atuoha Anthony by becoming a sponsor. Any amount is appreciated!