Open7

freezedを勉強するスレ

Ryo24Ryo24

https://techlib.circlearound.co.jp/entries/immutable/

  • immutable`だと変更ができない
    • 意図しない原因で値が変更されない
    • mutableだと参照時、データを弄れるためエラーを生み出すかもしれない?
  • 変更時にインスタンスの再作成が必要
    • 手間が増える
    • 作成時に動作チェックするため、classが意図通りの動作するか確認できる
Ryo24Ryo24

https://pub.dev/packages/freezed

セットアップ

パッケージをインストール

  • freezed_annotation : freezedに関するアノテーション
  • freezed : コードジェネレーター
  • build_runner : コードジェネレーターを実行するツール

analysis_options.yamlの作成

https://dart.dev/guides/language/analysis-options#the-analysis-options-file
analysis_options.yamlpubspec.yamlと同じディレクトリに作成し、自動生成したファイルを無視するようにする。そうすることで警告が表示されなくなり、快適にコーディングできる。

analysis_options.yaml
analyzer:
  exclude:
    - "**/*.g.dart"
    - "**/*.freezed.dart"
  errors:
    invalid_annotation_target: ignore

コード生成

実行コマンド

  • Flutterに依存したプロジェクトの場合
    • flutter pub run build_runner build
  • Flutterに依存していないプロジェクトの場合
    • dart pub run build_runner build

生成元のファイル

生成元のファイル
// main.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';

part 'main.freezed.dart';

@freezed
class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
}
  • 'package:freezed_annotation/freezed_annotation.dart'をインポートする
  • main.freezed.dartを読み込む
  • @freezedアノテーションをつける

機能

構文

基本

生成元のクラスを作成時、プロパティを宣言してはいけない代わりにfactoryを使う。

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

抽象クラスの活用が楽になる

抽象クラスを作成する時、abstract が不要になる。

before
@freezed
abstract class MixedIn with Mixin implements _$MixedIn {
  MixedIn._();
  factory MixedIn() = _MixedIn;
}

mixin Mixin {
  int method() => 42;
}
after
@freezed
abstract class MixedIn with Mixin implements _$MixedIn {
  MixedIn._();
  factory MixedIn() = _MixedIn;
}

mixin Mixin {
  int method() => 42;
}

生成元のファイルでメソッドを定義する

freezedにはクラスを拡張し実行する処理がデフォルトで実装していないため、プライベートコンストラクターを定義しなければならない。

メソッドを定義する
@freezed
class Person with _$Person {
  const Person._(); // Added constructor
  const factory Person(String name, {int? age}) = _Person;

  void method() {
    print('hello world');
  }
}

引数のチェック

assertを使いたい場合、@Assertで代用する。

引数のチェック
abstract class Person with _$Person {
  @Assert('name.isNotEmpty', 'name cannot be empty')
  @Assert('age >= 0')
  factory Person({
    String? name,
    int? age,
  }) = _Person;
}

初期値

初期値を定義する
abstract class Example with _$Example {
  const factory Example([@Default(42) int value]) = _Example;
}

コンストラクタの省略

コールバックのコンストラクタを省略可能

before
future.catchError((error) => MyClass.error(error))
after
future.catchError($MyClass.error)
Ryo24Ryo24

@Implements

freezed_annotation.dart
/// Marks a union type to implement the interface [type] or [stringType].
/// In the case below `City` will implement `GeographicArea`.
/// ```dart
/// @freezed
/// abstract class Example with _$Example {
///   const factory Example.person(String name, int age) = Person;
///
///   @Implements<AdministrativeArea<House>>()
///   const factory Example.city(String name, int population) = City;
/// }
/// ```
///
/// Note: You need to make sure that you comply with the interface requirements
/// by implementing all the abstract members. If the interface has no members or
/// just fields you can fulfil the interface contract by adding them in the
/// constructor of the union type. Keep in mind that if the interface defines a
/// method or a getter, that you implement in the class, you need to use the
/// [Custom getters and methods](#custom-getters-and-methods) instructions.
class Implements<T> {
  const Implements();
}

@With

freezed_annotation.dart
/// Marks a union type to mixin the class [type] or [stringType].
/// In the case below `City` will mixin with `GeographicArea`.
/// ```dart
/// @freezed
/// abstract class Example with _$Example {
///   const factory Example.person(String name, int age) = Person;
///
///   @With<AdministrativeArea<House>>()
///   const factory Example.city(String name, int population) = City;
/// }
/// ```
///
/// Note: You need to make sure that you comply with the interface requirements
/// by implementing all the abstract members. If the mixin has no members or
/// just fields, you can fulfil the interface contract by adding them in the
/// constructor of the union type. Keep in mind that if the mixin defines a
/// method or a getter, that you implement in the class, you need to use the
/// [Custom getters and methods](#custom-getters-and-methods) instructions.
class With<T> {
  const With();
}

結論

  • @Implements : 抽象クラスをベースにし、機能拡張を実施するクラス作成時に付与する
  • @With : 具象クラスをベースにし、機能拡張を実施するクラスを作成時に付与する

https://ja.javascript.info/mixins
https://ja.wikipedia.org/wiki/インタフェース_(抽象型)

Ryo24Ryo24

partに関して

sample.dart(生成元ファイル)
part 'sample.freezed.dart';
part 'sample.g.dart';
sample.freezed.dart(生成ファイル)
part of 'sample.dart';
sample.g.dart(生成ファイル)
part of 'sample.dart';

これはsample.dartの拡張としてsample.freezed.dartsample.g.dartが呼び込まれていることを指す。
従ってpartは親ファイルを指し、part ofは子ファイルを意味している。

ファイルをimportするときは、sample.dartをインポートしたらsample.freezed.dartsample.g.dartがセットでインポートされる。

Ryo24Ryo24
sample.dart
@freezed
class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
  const factory Union.complex(int a, String b) = Complex;

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

_$Unionは、mixinを参照(or 作成)している。

sample.freezed.dart(参照元)
/// @nodoc
mixin _$Union {
  @optionalTypeArgs
  TResult when<TResult extends Object?>(
    TResult Function(int value) $default, {
    required TResult Function() loading,
    required TResult Function(String? message) error,
    required TResult Function(int a, String b) complex,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult? whenOrNull<TResult extends Object?>(
    TResult Function(int value)? $default, {
    TResult Function()? loading,
    TResult Function(String? message)? error,
    TResult Function(int a, String b)? complex,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult maybeWhen<TResult extends Object?>(
    TResult Function(int value)? $default, {
    TResult Function()? loading,
    TResult Function(String? message)? error,
    TResult Function(int a, String b)? complex,
    required TResult orElse(),
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult map<TResult extends Object?>(
    TResult Function(Data value) $default, {
    required TResult Function(Loading value) loading,
    required TResult Function(ErrorDetails value) error,
    required TResult Function(Complex value) complex,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult? mapOrNull<TResult extends Object?>(
    TResult Function(Data value)? $default, {
    TResult Function(Loading value)? loading,
    TResult Function(ErrorDetails value)? error,
    TResult Function(Complex value)? complex,
  }) =>
      throw _privateConstructorUsedError;
  @optionalTypeArgs
  TResult maybeMap<TResult extends Object?>(
    TResult Function(Data value)? $default, {
    TResult Function(Loading value)? loading,
    TResult Function(ErrorDetails value)? error,
    TResult Function(Complex value)? complex,
    required TResult orElse(),
  }) =>
      throw _privateConstructorUsedError;
  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
}

https://resocoder.com/2019/07/21/mixins-in-dart-understand-dart-flutter-fundamentals-tutorial/

Ryo24Ryo24
sample.dart
@freezed
class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
  const factory Union.complex(int a, String b) = Complex;

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

DataLoadingErrorDetailsとかは抽象クラスを参照(or 作成)している。

sample.freezed.dart
abstract class Loading implements Union {
  const factory Loading() = _$Loading;

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