Flutter freezed
Dartでimmutableなクラスを作る時に役立つパッケージ
いくつかのパッケージと組み合わせて使う
build_runner
コードジェネレータを走らせるためのパッケージ
freezed
コードジェネレータ
freezed_annotation
freezedのためのアノテーション(多分@freezedとか)を含んだパッケージ
使い方
以下の記述だけ、必ず必要(foundation.dartはなくてもいいらしいけど、DevToolsで便利になるらしい
code:dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'my_file.freezed.dart';
my_fileは生成したいファイル名によって変わってくる
code:main.dart
// 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;
}
これを作った後に、コードジェネレータを起動する
flutter pub run build_runner build
linterから生成されたファイルの警告を消す
code:analysis_options.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
シンタックス
クラスを定義するには、プロパティを書く必要はないが、factoryを書く必要がある
code:person.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'person.freezed.dart';
@freezed
class Person with _$Person {
factory Person({ String? name ,int? age}) = _Person;
}
使うときはこう
code:dart
var person = Person(name: 'Remi', age: 24);
print(person.name);
print(person.age);
別にnamed parameterじゃなくてもOK
code:person.dart
@freezed
class Person with _$Person {
factory Person(String name, int age) = _Person;
}
Person('Remi', 24)
// 一部だけnamed parameterでもOK
@freezed
class Person with _$Person {
const factory Person(String name, {int? age}) = _Person;
}
Person('Remi', age: 24)
mixinもこんな感じに使える
code:mixed_in.dart
@freezed
class MixedIn with _$MixedIn, Mixin {
const MixedIn._();
factory MixedIn() = _MixedIn;
}
mixin Mixin {
int method() => 42;
}
var m = MixedIn();
m.method();
カスタムメソッド
code:person.dart
@freezed
class Person with _$Person {
// コンストラクタを記述しないと、カスタムメソッドは使えない
const Person._();
const factory Person(String name, {int? age}) = _Person;
void method() {
print('hello world');
}
}
assert
code:person.dart
abstract class Person with _$Person {
@Assert('name.isNotEmpty', 'name cannot be empty')
@Assert('age >= 0')
factory Person({
String? name,
int? age,
}) = _Person;
}
これ生成されたファイルがコンパイルエラーになるなosamtimizer.icon
あとAssertの中身がコード補完されないからちょい微妙
default value
code:example.dart
abstract class Example with _$Example {
}
CopyWith
どうやら何もパラメータがないFreezed classはメソッドが生えない模様
code:person.dart
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter/foundation.dart';
part 'person.freezed.dart';
@freezed
class Person with _$Person {
factory Person(String name, int age) = _Person;
}
var person = Person('Remi', 24);
// age not passed, its value is preserved
print(person.copyWith(name: 'Dash')); // Person(name: Dash, age: 24)
// age is set to null
print(person.copyWith(age: null)); // Person(name: Remi, age: null)
DeepCopy
Freezed classがさらにfreezed classに依存している場合、普通であればそれぞれをcopyWithでコピーしないと中身を変更できない
code:classes.dart
@freezed
class Company with _$Company {
factory Company({String? name, Director? director}) = _Company;
}
@freezed
class Director with _$Director {
factory Director({String? name, Assistant? assistant}) = _Director;
}
@freezed
class Assistant with _$Assistant {
factory Assistant({String? name, int? age}) = _Assistant;
}
code:dart
Company company;
//Company -> Director -> Assistantの順
Company newCompany = company.copyWith(
director: company.director.copyWith(
assistant: company.director.assistant.copyWith(
name: 'John Smith',
),
),
);
FreezedにはDeep copyの機能があって、これを使えばもっと簡単に書ける
code:dart
Company company;
Company newCompany = company.copyWith.director.assistant(name: 'John Smith');
Unions/Sealed classes
Pattern matching
Map
FromJson/ToJson
Freezedは直接これらを扱わない
代わりにjson_serializableがいい感じにしてくれる
例えばこんなクラスがあったとして、
code:model.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
@freezed
class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
}
少し変えるだけでjson_serializableが使えるようになる
code:model.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'model.freezed.dart';
part 'model.g.dart';
@freezed
class Model with _$Model {
factory Model.first(String a) = First;
factory Model.second(int b, bool c) = Second;
factory Model.fromJson(Map<String, dynamic> json) => _$ModelFromJson(json);
}
これでfromJsonとtoJsonが生える。便利
fromJsonをカスタムしたい場合
自分で作って、それをfreezedなmodel file側で使うように記述する
JsonConverterをimplementsして、
code:dart
class MyResponseConverter implements JsonConverter<MyResponse, Map<String, dynamic>> {
const MyResponseConverter();
@override
MyResponse fromJson(Map<String, dynamic> json) {
if (json == null) {
return null;
}
// type data was already set (e.g. because we serialized it ourselves)
return MyResponse.fromJson(json);
}
// you need to find some condition to know which type it is. e.g. check the presence of some field in the json
if (isTypeData) {
return MyResponseData.fromJson(json);
} else if (isTypeSpecial) {
return MyResponseSpecial.fromJson(json);
} else if (isTypeError) {
return MyResponseError.fromJson(json);
} else {
throw Exception('Could not determine the constructor for mapping from JSON');
}
}
@override
Map<String, dynamic> toJson(MyResponse data) => data.toJson();
}
freezed classに組み込む
code:dart
@freezed
class MyModel with _$MyModel {
const factory MyModel(@MyResponseConverter() MyResponse myResponse) = MyModelData;
factory MyModel.fromJson(Map<String, dynamic> json) => _$MyModelFromJson(json);
}
freezedでfromJsonメソッドがうまく生成されない場合
freezedはfromJsonだけでなく、toJsonも自動で生成しようとする
freezed modelのシリアライズ・デシリアライズ対象となるクラスにfromJson toJsonが実装されていない場合、メソッドの生成ができない