❄️

最近流行りのfreezedとは?

2022/05/21に公開

@freezedってなにができるのか?

公式ドキュメント
https://pub.dev/packages/freezed#install

ドキュメントのfreezedの役割が解説されている文章を翻訳してみた🧑🏼‍🎓

ダートは素晴らしいですが、「モデル」を定義するのは面倒です。私たちはしなければならないかもしれません:

コンストラクターとプロパティを定義する
オーバーライドtoString、、operator ==_hashCode
copyWithオブジェクトを複製するメソッドを実装する
デ/シリアル化の処理
その上、Dartには共用体タイプやパターンマッチングなどの機能もありません。

これらすべてを実装するには数百行かかる可能性があり、エラーが発生しやすく、モデルの可読性が大幅に向上します。

Freezedは、これのほとんどを実装することでそれを修正しようとし、モデルの定義に集中できるようにします。

Dartを書いているときに、クラスを作るとコンストラクターを作っていたと思いますが、freezedを使うと自動でやってくれました!

実際に手を動かして作ってみょう!

開発環境

Flutterのバージョン: flutter3.0.0
OS: macOS Monterey
エディター: VScode

環境構築をする

今回はDartファイルを使って、Swiftのplaygrandみたいに、プログラムを動かすだけです。これでfreezedがどうんなものなのかを体験してみます。

  1. プロジェクト用のフォルダを作成
  2. コマンドを実行して、Dartのプロジェクトを作成する
dart create freeze_sample
cd freeze_sample
code .
  1. pubspec.yamlにダートパッケージを追加しますが、ドキュメントに書いていないパッケージがあったので、追加しておきました!ドキュメントもあまり親切でないことが多い!

ダートのプロジェクトなので、こちらのパッケージを追加

dart pub add freezed_annotation
dart pub add --dev build_runner
dart pub add --dev freezed

こちらが追加するダートパッケージ! 追加しないと、part 'ファイル名.g.dart';でエラーが出る!

https://pub.dev/packages/json_serializable

dart pub add json_serializable

pubspec.yaml

name: freeze_sample
description: A simple command-line application.
version: 1.0.0
# homepage: https://www.example.com

environment:
  sdk: '>=2.14.3 <3.0.0'


# dependencies:
#   path: ^1.8.0

dev_dependencies:
  # コードジェネレーターを実行するためのツール
  build_runner: ^2.1.11
  # コードジェネレータ
  freezed: ^2.0.3+1
  # part 'main.g.dart';で必要なパッケージ
  json_serializable: ^6.2.0
  lints: ^1.0.0
dependencies:
  # フリーズされたアノテーションを含むパッケージ
  freezed_annotation: ^2.0.3

binフォルダの中に直接モデルとなるファイルを書いても良いのですが、dataフォルダを作成しました。
Udemyの講師のさくしんさんという方は、作っていたので...

ドキュメントのをコピペして書き換えてもいいのですが、VScodeには、便利な拡張機能があった!
こちらの記事を参考にいたしました🧑‍💻

https://zenn.dev/kaleidot725/articles/2021-11-24-dart-freezed

設定が終わったらモデルを作成してみましょう

エラーのダイアログが出てくるが、ファイルは作成されるので気にしなくても良い...

右クリックでも使えるみたいです。お好みで😅
こっちの方が楽でした!

こんなファイルができました!

クラスで使うプロパティを定義しましょう
ドキュメントと書き方違いますが、requiredつけるとエラー出るので、今回は、こちらの書き方でプロパティを定義します。

data/carmaker.dart


import 'package:freezed_annotation/freezed_annotation.dart';

part 'carmaker.freezed.dart';
part 'carmaker.g.dart';


class Carmaker with _$Carmaker {
  // {}を中に書いて、その中に、プロパティを?つけて書く!
  factory Carmaker({
    String? maker,
    String? product,
    int? price
  }) = _Carmaker;

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

ドキュメントのコマンドを実行すればファイルが自動生成されます。

dart run build_runner build

でも、便利な拡張機能がVScodeにはあったのでそちらを使います。

おお!、ファイルが生成された!

carmarker.dartのエラーが消えました。
ファイルが生成され、そちらをインポートしたからのようですね。
ファイルの中身はどうなっているのかというと...

carmarker.freezed.dart

// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target

part of 'carmaker.dart';

// **************************************************************************
// FreezedGenerator
// **************************************************************************

T _$identity<T>(T value) => value;

final _privateConstructorUsedError = UnsupportedError(
    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods');

Carmaker _$CarmakerFromJson(Map<String, dynamic> json) {
  return _Carmaker.fromJson(json);
}

/// @nodoc
mixin _$Carmaker {
  String? get maker => throw _privateConstructorUsedError;
  String? get product => throw _privateConstructorUsedError;
  int? get price => throw _privateConstructorUsedError;

  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
  (ignore: true)
  $CarmakerCopyWith<Carmaker> get copyWith =>
      throw _privateConstructorUsedError;
}

/// @nodoc
abstract class $CarmakerCopyWith<$Res> {
  factory $CarmakerCopyWith(Carmaker value, $Res Function(Carmaker) then) =
      _$CarmakerCopyWithImpl<$Res>;
  $Res call({String? maker, String? product, int? price});
}

/// @nodoc
class _$CarmakerCopyWithImpl<$Res> implements $CarmakerCopyWith<$Res> {
  _$CarmakerCopyWithImpl(this._value, this._then);

  final Carmaker _value;
  // ignore: unused_field
  final $Res Function(Carmaker) _then;

  
  $Res call({
    Object? maker = freezed,
    Object? product = freezed,
    Object? price = freezed,
  }) {
    return _then(_value.copyWith(
      maker: maker == freezed
          ? _value.maker
          : maker // ignore: cast_nullable_to_non_nullable
              as String?,
      product: product == freezed
          ? _value.product
          : product // ignore: cast_nullable_to_non_nullable
              as String?,
      price: price == freezed
          ? _value.price
          : price // ignore: cast_nullable_to_non_nullable
              as int?,
    ));
  }
}

/// @nodoc
abstract class _$$_CarmakerCopyWith<$Res> implements $CarmakerCopyWith<$Res> {
  factory _$$_CarmakerCopyWith(
          _$_Carmaker value, $Res Function(_$_Carmaker) then) =
      __$$_CarmakerCopyWithImpl<$Res>;
  
  $Res call({String? maker, String? product, int? price});
}

/// @nodoc
class __$$_CarmakerCopyWithImpl<$Res> extends _$CarmakerCopyWithImpl<$Res>
    implements _$$_CarmakerCopyWith<$Res> {
  __$$_CarmakerCopyWithImpl(
      _$_Carmaker _value, $Res Function(_$_Carmaker) _then)
      : super(_value, (v) => _then(v as _$_Carmaker));

  
  _$_Carmaker get _value => super._value as _$_Carmaker;

  
  $Res call({
    Object? maker = freezed,
    Object? product = freezed,
    Object? price = freezed,
  }) {
    return _then(_$_Carmaker(
      maker: maker == freezed
          ? _value.maker
          : maker // ignore: cast_nullable_to_non_nullable
              as String?,
      product: product == freezed
          ? _value.product
          : product // ignore: cast_nullable_to_non_nullable
              as String?,
      price: price == freezed
          ? _value.price
          : price // ignore: cast_nullable_to_non_nullable
              as int?,
    ));
  }
}

/// @nodoc
()
class _$_Carmaker implements _Carmaker {
  _$_Carmaker({this.maker, this.product, this.price});

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

  
  final String? maker;
  
  final String? product;
  
  final int? price;

  
  String toString() {
    return 'Carmaker(maker: $maker, product: $product, price: $price)';
  }

  
  bool operator ==(dynamic other) {
    return identical(this, other) ||
        (other.runtimeType == runtimeType &&
            other is _$_Carmaker &&
            const DeepCollectionEquality().equals(other.maker, maker) &&
            const DeepCollectionEquality().equals(other.product, product) &&
            const DeepCollectionEquality().equals(other.price, price));
  }

  (ignore: true)
  
  int get hashCode => Object.hash(
      runtimeType,
      const DeepCollectionEquality().hash(maker),
      const DeepCollectionEquality().hash(product),
      const DeepCollectionEquality().hash(price));

  (ignore: true)
  
  _$$_CarmakerCopyWith<_$_Carmaker> get copyWith =>
      __$$_CarmakerCopyWithImpl<_$_Carmaker>(this, _$identity);

  
  Map<String, dynamic> toJson() {
    return _$$_CarmakerToJson(this);
  }
}

abstract class _Carmaker implements Carmaker {
  factory _Carmaker(
      {final String? maker,
      final String? product,
      final int? price}) = _$_Carmaker;

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

  
  String? get maker => throw _privateConstructorUsedError;
  
  String? get product => throw _privateConstructorUsedError;
  
  int? get price => throw _privateConstructorUsedError;
  
  (ignore: true)
  _$$_CarmakerCopyWith<_$_Carmaker> get copyWith =>
      throw _privateConstructorUsedError;
}

carmarker.g.dart

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'carmaker.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

_$_Carmaker _$$_CarmakerFromJson(Map<String, dynamic> json) => _$_Carmaker(
      maker: json['maker'] as String?,
      product: json['product'] as String?,
      price: json['price'] as int?,
    );

Map<String, dynamic> _$$_CarmakerToJson(_$_Carmaker instance) =>
    <String, dynamic>{
      'maker': instance.maker,
      'product': instance.product,
      'price': instance.price,
    };

何書いてるかさっぱりわからん😅
触ることはないようなので、そのままにしておきます。

コンストラクターは値がないので、引数を入れて行きます。ご自分の好きな車の会社名、車の名前、値段を入力してください。ちなみに私は、4輪車持ってなくて、原付に乗ってます😅

carmaker.dartを修正

import 'package:freezed_annotation/freezed_annotation.dart';

part 'carmaker.freezed.dart';
part 'carmaker.g.dart';


class Carmaker with _$Carmaker {
  // {}を中に書いて、その中に、プロパティを?つけて書く!
  factory Carmaker({String? maker, String? product, int? price}) = _Carmaker;

  factory Carmaker.fromJson(Map<String, dynamic> json) =>
      _$CarmakerFromJson(json);
}
// プログラムを実行するmain関数を追加
void main() {
  var toyota = Carmaker(maker: "TOYOTA", product: "ランドクルーザー", price: 3000000);
  var honda = Carmaker(maker: "HONDA", product: "エクストレイル", price: 2980000);
  print(toyota.maker);
  print(toyota.product);
  print(toyota.price);
  print("-----------------------");
  print(honda.maker);
  print(honda.product);
  print(honda.price);
}

実行結果

TOYOTA
ランドクルーザー
3000000
-----------------------
HONDA
エクストレイル
2980000

使ってみた感想

ドキュメントをよく見れば理解できると思う?
しかし、g.dartってのが、罠があって必要なパッケージがあった!
以前より理解が速かったのは、Dartの文法への理解が深まったからだと思います。以前はfactoryなにこれ?
知らない文法が多くて悩まされました😇

Discussion