Freezed Documentation

repository·master·Indexed 24 days ago

https://github.com/rrousselgit/freezed

A code generator for Dart and Flutter that automates the creation of data classes, tagged unions, nested classes, and cloning methods (copyWith). It reduces boilerplate for constructors, equality, hashCode, toString, and serialization. The package includes support for validation via @Assert, default values with @Default, mutable models using @unfreezed, and specialized linting through freezed_lint and custom_lint.

Tokens
26K
Snippets
98
Records
115
Agent score
78%

What's inside Freezed

  1. Access Shared vs. Specific Properties in Union Types

    master

    When using Union types, you can only directly access properties that are shared across all constructors. Properties unique to a specific constructor cannot be accessed directly on the base class instance.

    Shared Properties

    If all constructors define a property with the same name and type, it is accessible via the base class.

    @freezed
    sealed class Example with _$Example {
      const factory Example.person(String name, int age) = Person;
      const factory Example.city(String name, int population) = City;
    }
    
    var example = Example.person('Remi', 24);
    print(example.name); // Works: 'Remi'

    Specific Properties

    To access properties that are not shared (like age in Person), you must use pattern matching (Dart 3 switch or Freezed's legacy when/map methods).

    switch (example) {
      case Person(:final name, :final age): print('Person $name, age $age');
      case City(:final name, :final population): print('City $name, pop $population');
    }
  2. Define mutable classes using @unfreezed

    master

    If you need to define a class where properties can be modified after instantiation, replace the @freezed annotation with @unfreezed.

    Note that @unfreezed classes:

    • Have mutable properties.
    • Do not implement == or hashCode (equality is based on object identity).
    • Cannot be instantiated using const.
    @unfreezed
    abstract class Person with _$Person {
      factory Person({
        required String firstName,
        required String lastName,
      }) = _Person;
    
      factory Person.fromJson(Map<String, Object?> json)
          => _$PersonFromJson(json);
    }
  3. Handle Union Types in JSON (Multiple Constructors)

    master

    When a Freezed class has multiple constructors (union types), Freezed uses a runtimeType field in the JSON to determine which constructor to instantiate.

    Customizing Union Keys and Values

    You can customize the key used for the type discriminator and the casing of the values using @Freezed and @FreezedUnionValue:

    • unionKey: The JSON key used to identify the type (defaults to runtimeType).
    • unionValueCase: The casing for the values (e.g., FreezedUnionCase.pascal).
    • @FreezedUnionValue('Value'): Overrides the value for a specific constructor.

    Global Configuration

    You can set these options globally for your project in build.yaml:

    targets:
      $default:
        builders:
          freezed:
            options:
              union_key: type
              union_value_case: pascal

    Custom Converters

    If you do not control the JSON structure (e.g., the discriminator is missing or logic is complex), implement a JsonConverter<T, Map<String, dynamic>> and apply it to the constructor parameter.

    @Freezed(unionKey: 'type', unionValueCase: FreezedUnionCase.pascal)
    sealed class MyResponse with _$MyResponse {
      const factory MyResponse(String a) = MyResponseData;
    
      @FreezedUnionValue('SpecialCase')
      const factory MyResponse.special(String a, int b) = MyResponseSpecial;
    
      const factory MyResponse.error(String message) = MyResponseError;
    }
  4. Handle JSON serialization for Union classes (multiple constructors)

    master

    When a Freezed class has multiple constructors (a union), Freezed uses a runtimeType key in the JSON to determine which constructor to instantiate.

    By default, it looks for a key named runtimeType. You can customize this behavior using:

    • @Freezed(unionKey: '...', unionValueCase: ...) to change the key name and how values are formatted.
    • @FreezedUnionValue('...') on specific constructors to override the value used for that specific variant.
    • build.yaml to apply these settings globally across your project.

    If you cannot control the JSON structure, you can implement a custom JsonConverter to handle the constructor selection logic manually.

    @Freezed(unionKey: 'type', unionValueCase: FreezedUnionCase.pascal)
    sealed class MyResponse with _$MyResponse {
      const factory MyResponse(String a) = MyResponseData;
    
      @FreezedUnionValue('SpecialCase')
      const factory MyResponse.special(String a, int b) = MyResponseSpecial;
    
      const factory MyResponse.error(String message) = MyResponseError;
    
      factory MyResponse.fromJson(Map<String, dynamic> json) => _$MyResponseFromJson(json);
    }
  5. Use copyWith and Deep Copy for object updates

    master

    Freezed generates a copyWith method to create new instances with updated values.

    For nested Freezed models, you can use Deep Copy syntax to avoid redundant boilerplate. Instead of nesting multiple copyWith calls, you can chain the properties: instance.copyWith.property1.property2(field: value).

    Handling Nulls in Deep Copy: If a nested property might be null, use the ?.call operator to prevent compilation errors when attempting to access a property on a null object.

  6. Add getters and methods to Primary Constructor models

    master

    By default, Freezed classes using primary constructors are implemented by a generated class. If you try to add manual methods or getters, you will get a compilation error because the generated class is missing those members.

    To fix this, define a private empty constructor const ClassName._();. This tells Freezed to make the generated class extend your class instead of just implementing it, allowing your manual members to be inherited.

    Example

    @freezed
    abstract class Person with _$Person {
      // Added constructor. Must not have any parameter
      const Person._();
    
      const factory Person(String name, {int? age}) = _Person;
    
      void method() {
        print('hello world');
      }
    }
  7. Access shared properties in Union Types

    master

    When using union types, you can directly access properties that are defined in all constructors. Properties that are unique to only one constructor cannot be accessed directly on the base class and will cause a compilation error.

    Example of shared properties:

    @freezed
    sealed class Example with _$Example {
      const factory Example.person(String name, int age) = Person;
      const factory Example.city(String name, int population) = City;
    }
    
    var example = Example.person('Remi', 24);
    print(example.name); // Works because 'name' is in both constructors
  8. Handle non-constant default values in Freezed

    master

    If you need a default value that is not a constant (e.g., DateTime.now()), you cannot use @Default. Instead, you must use a private constructor ClassName._() to initialize the field manually.

    Example

    @freezed
    sealed class Response<T> with _$Response<T> {
      // We give "time" parameters a non-constant default
      Response._({DateTime? time}) : time = time ?? DateTime.now();
      
      factory Response.data(T value, {DateTime? time}) = ResponseData;
      factory Response.error(Object error) = ResponseError;
    
      @override
      final DateTime time;
    }
    @freezed
    sealed class Response<T> with _$Response<T> {
      // We give "time" parameters a non-constant default
      Response._({DateTime? time}) : time = time ?? DateTime.now();
      // Constructors may enable passing parameters to ._();
      factory Response.data(T value, {DateTime? time}) = ResponseData;
      // If ._ parameters are named and optional, factory constructors are not required to specify it
      factory Response.error(Object error) = ResponseError;
    
      @override
      final DateTime time;
    }
  9. How Union types and Sealed classes work

    master

    Freezed allows you to define models that can exist in multiple mutually exclusive states by defining multiple factory constructors. This is useful for representing states like data, loading, and error.

    Note for Dart 3+ users: Since Dart 3 introduced native sealed classes and pattern matching, it is recommended to use the official Dart switch syntax instead of Freezed's generated when or map methods. However, Freezed's methods are still available for legacy support.

    @freezed
    sealed class Union with _$Union {
      const factory Union.data(int value) = Data;
      const factory Union.loading() = Loading;
      const factory Union.error([String? message]) = Error;
    }
  10. Implement Union Types with Freezed

    master

    Freezed allows you to define Union types (similar to sealed classes or sum types) by providing multiple constructors for a single class. This is useful for representing mutually exclusive states like Data, Loading, and Error.

    If you are using Dart 3, it is highly recommended to use the sealed keyword when defining your Union class to enable native pattern matching.

    @freezed
    sealed class Union with _$Union {
      const factory Union.data(int value) = Data;
      const factory Union.loading() = Loading;
      const factory Union.error([String? message]) = Error;
    }
    @freezed
    sealed class Union with _$Union {
      const factory Union.data(int value) = Data;
      const factory Union.loading() = Loading;
      const factory Union.error([String? message]) = Error;
    }
  11. Use copyWith and Deep Copy

    master

    Freezed provides a copyWith method to create a new instance of a class with modified values.

    Standard copyWith

    Useful for simple updates. If a value is not provided to copyWith, the existing value is preserved. Freezed also supports setting values to null via copyWith(field: null).

    Deep Copy

    For nested Freezed models, instead of nesting multiple copyWith calls, you can use the deep copy syntax: model.copyWith.property.subProperty(field: value).

    Handling Nulls in Deep Copy: If a nested property might be null, use the ?.call operator to prevent compilation errors when attempting to access a property on a null object.

  12. How copyWith works in Freezed

    master

    Freezed automatically generates a copyWith method for your models. This method allows you to create a new instance of an object with specific properties updated while keeping the rest of the values unchanged.

    Crucially, Freezed supports setting properties to null using copyWith. If you pass a value to a parameter, it updates; if you omit it, the original value is preserved; and if you explicitly pass null, the property is set to null in the new instance.

    @freezed
    abstract class Person with _$Person {
      factory Person(String name, int? age) = _Person;
    }
    
    void main() {
      var person = Person('Remi', 24);
    
      // `age` is not provided, so it is preserved.
      print(person.copyWith(name: 'Dash')); // Person(name: Dash, age: 24)
    
      // `age` is explicitly set to `null`.
      print(person.copyWith(age: null)); // Person(name: Remi, age: null)
    }