Open5

改めてRiverpod

Hidden comment
Hidden comment
Hidden comment
Hidden comment
TsuboiTsuboi

Freezed

abstractキーワード

abstractキーワードはfreezedファイルを生成する際には必要ない。


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

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


class MixedIn with _$MixedIn, Mixin {
  const MixedIn._();
  factory MixedIn() = _MixedIn;
}

mixin Mixin {
  int method() => 42;
}
ディープコピー

class Company with _$Company {
  factory Company({String? name, Director? director}) = _Company;
}


class Director with _$Director {
  factory Director({String? name, Assistant? assistant}) = _Director;
}


class Assistant with _$Assistant {
  factory Assistant({String? name, int? age}) = _Assistant;
}
// ✗
Company company;

Company newCompany = company.copyWith(
  director: company.director.copyWith(
    assistant: company.director.assistant.copyWith(
      name: 'John Smith',
    ),
  ),
);

// ○
Company company;

Company newCompany = company.copyWith.director.assistant(name: 'John Smith');

company = company.copyWith(name: 'Google', director: Director(...));
company = company.copyWith.director(name: 'Larry', assistant: Assistant(...));
company = company.copyWith.director.assistant(name: 'John', age: 42);
ユニオン/シールドクラス

freezedではユニオン/シールドクラスを以下のように複数のコンストラクタを書くことで実現できる


class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
}

shared properties


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.age); // does not compile!

一方で、すべてのプロパティが書かれている場合は読み取ることができる

var example = Example.person('Remi', 24);
print(example.name); // Remi
example = Example.city('London', 8900000);
print(example.name); // London

// またcopyWithも使えるよ
var example = Example.person('Remi', 24);
print(example.copyWith(name: 'Dash')); // Example.person(name: Dash, age: 24)

example = Example.city('London', 8900000);
print(example.copyWith(name: 'Paris')); // Example.city(name: Paris, population: 8900000)

// is演算子も使えるよ
var example = Example.person('Remi', 24);
if (example is Person) {
  print(example.age); // 24
}

when
これは破壊を伴うパターンマッチに相当


class Union with _$Union {
  const factory Union(int value) = Data;
  const factory Union.loading() = Loading;
  const factory Union.error([String? message]) = ErrorDetails;
}

var union = Union(42);

print(
  union.when(
    (int value) => 'Data $data',
    loading: () => 'loading',
    error: (String? message) => 'Error: $message',
  ),
); // Data 42

map
破壊を避けるためにmapメソッドがある


class Model with _$Model {
  factory Model.first(String a) = First;
  factory Model.second(int b, bool c) = Second;
}

// whenやったらこれ
var model = Model.first('42');

print(
  model.when(
    first: (String a) => 'first $a',
    second: (int b, bool c) => 'second $b $c'
  ),
); // first 42

// mapならこれ
var model = Model.first('42');

print(
  model.map(
    first: (First value) => 'first ${value.a}',
    second: (Second value) => 'second ${value.b} ${value.c}'
  ),
); // first 42

// copyWithも使えるよ
var model = Model.second(42, false)
print(
  model.map(
    first: (value) => value,
    second: (value) => value.copyWith(c: true),
  )
); // Model.second(b: 42, c: true)


FromJson/ToJson

FromJson/ToJsonはjson_serializableを用いることで使用できる

import 'package:freezed_annotation/freezed_annotation.dart';

part 'model.freezed.dart';


class Model with _$Model {
  factory Model.first(String a) = First;
  factory Model.second(int b, bool c) = Second;
}

json_serializableと互換性を持たせるため、以下のように変更

import 'package:freezed_annotation/freezed_annotation.dart';

part 'model.freezed.dart';
part 'model.g.dart';


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

JSONレスポンスを制御しないのであれば、カスタムコンバータを実装することができます。カスタム コンバータは、使用するコンストラクタを決定するための独自のロジックを実装する必要があります。

class MyResponseConverter implements JsonConverter<MyResponse, Map<String, dynamic>> {
  const MyResponseConverter();

  
  MyResponse fromJson(Map<String, dynamic> json) {
    if (json == null) {
      return null;
    }
    // type data was already set (e.g. because we serialized it ourselves)
    if (json['runtimeType'] != null) {
      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');
    }
 }

  
  Map<String, dynamic> toJson(MyResponse data) => data.toJson();
}

カスタムコンバータを適用するには、コンストラクタのパラメータにデコレータを渡します。


class MyModel with _$MyModel {
  const factory MyModel(() MyResponse myResponse) = MyModelData;

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