くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Flutterのfreezedパッケージでimmutableなオブジェクトを楽に扱う

Flutterのfreezedパッケージを調べたときの備忘録。

freezedとは

Dartでデータクラスのような機能を提供するパッケージ。

  • コンストラクタだけのシンプルなモデル定義
  • オブジェクトをクローンできるcopyWithメソッド
  • union-types/pattern matching
  • 自動的なserialization/deserialization
  • すべてのプロパティを比較/表示する==/toString

@freezedなどのアノテーションを付けたクラス(モデル定義)に対し、
build_runnerを使ったコード生成によって、
これらの便利な機能を提供してくれる。

インストール

3つのパッケージをインストールする。

# pubspec.yaml
dependencies:
  freezed_annotation:

dev_dependencies:
  build_runner:
  freezed:
$ flutter pub add freezed_annotation
$ flutter pub add -d freezed
$ flutter pub add -d build_runner
$ flutter pub get

.freezed.dartなどで、警告が出ないようにするために、
analysis_options.yamlに設定を追加。

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

モテルを定義する

モデルは@freezedなどを使って定義する。
お作法的な部分が多いけど、こんな感じ。

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

// 自動生成したコードを取り込む
part 'User.freezed.dart';

// モデル定義
@freezed
class User with _$User {
  const factory User({required String name}) = _User;
}

定義しただけだと、コード生成前なのでコンパイルエラーになる。
コード生成のために、以下のコマンドを実行する。

$ flutter pub run build_runner build

コマンドを実行すると、_$User_Userを含む、
User.freezed.dartが同じ場所に生成される。

生成が完了すると、こんな感じで使えるようになる。
イミュータブルなクラスなので、再代入はできない。

import './User.dart';
const user = User(name: "user-A");

モデルに独自のメソッドを追加する

自動生成される_$Userはmixinなので、拡張ができず、
そのままだと、メソッドを追加できない。。

独自のメソッドを利用できるよう、
単一のプライベートコンストラクタを追記する必要がある。

// ./User.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'User.freezed.dart';

// モデル定義
@freezed
class User with _$User {
  const User._(); // メソッドを追加するので、コンストラクタを追加
  const factory User({required String name}) = _User;

  // 独自のメソッド
  void print() {
    debugPrint(toString());
  }
}

モデルにfromJson/toJsonを追加する

json_serializableを使った、
fromJson/toJsonも簡単に追加できる。

まずは、json_serializableパッケージを追加。

$ flutter pub add -d json_serializable
$ flutter pub get

次に

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

part 'User.freezed.dart';
// JSON用の生成コード
part 'User.g.dart';

// モデル定義
@freezed
class User with _$User {
  const factory User({required String name}) = _User;
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

json_serializableもコード生成して使うので、コマンド実行。

$ flutter pub run build_runner build

すると、User.g.dartが生成されるので、
こんな感じで使えるように。

// json.decode()/encode()に必要
import 'dart:convert';
import './User.dart';
final user = User.fromJson(json.decode('{"name":"user-A"}'));

debugPrint(json.encode(user.toJson()));

文字列を変換する処理もまとめたい場合はこんな感じにもできる。

// ./User.dart
import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';

part 'User.freezed.dart';
part 'User.g.dart';

// モデル定義
@freezed
class User with _$User {
  const User._(); // メソッドを追加するので、コンストラクタを追加
  const factory User({required String name}) = _User;
  
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  // クラスメソッドに、fromJsonStrメソッドを追加
  factory User.fromJsonStr(String jsonStr) => _$UserFromJson(json.decode(jsonStr));
  
  String toJsonStr() {
    return json.encode(toJson());
  }
}
import 'dart:convert';
import './User.dart';
final user = User.fromJsonStr('{"name":"user-A"}');
debugPrint(user.toJsonStr());

以上!! 便利(´ω`)

参考にしたサイトさま