🧊

【Flutter】「Freezed」を学ぼう

2022/11/18に公開

こんにちは、Saeです。
私は「さえないエンジニア」から「さえたエンジニア」へクラスチェンジするために、日々レベル上げに勤しんでます。

今日は「Freezed」というPackageについて解説します。

また今回使用しているサンプルコードはGitHubで公開しております。参考になれば幸いです。
https://github.com/Sae-Eng/freezed_example

目次

忙しい人のための「Freezed」

日々忙しさで忙殺されているエンジニアへ向けた「Freezed」の説明です。
ここだけ見れば、なんとなくわかります。

  • 「Freezed」とは、データクラスとそれに必要な機能を自動生成してくれるパッケージのこと
  • 作成する時は以下のステップで行う
  1. パッケージをインストール
dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  # freezed
  freezed_annotation: 2.2.0


dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  # freezed
  freezed: 2.2.1
  build_runner: 2.3.2
  json_serializable: 6.5.4
  1. クラスを作成
import 'package:freezed_annotation/freezed_annotation.dart';

// 生成されるdartファイルを記述
part 'user.freezed.dart';
part 'user.g.dart';

// freezedでコード生成するために「@freezed」を記述

class User with _$User { // withの後には「_$[class name]」の形式で記述

  // プロパティを指定
  const factory User({
    required int id,
    required String username,
    required String email,
    required String phone,
    (false) bool isPremium,
  }) = _User;

  // json形式で受け取るためのコードを生成するために記述
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
  1. コードを生成
flutter pub run build_runner build --delete-conflicting-outputs
  1. 完成

Freezedとは


それではここから「Freezed」について詳しく説明します。

そもそも「Freezed」とは何なのでしょうか。
公式ドキュメントには以下のように記載されてます。

これは、データクラス/ユニオン/パターン/マッチング/クローン作成用の、もう1つのコードジェネレーターです。

https://github.com/rrousselGit/freezed

要約すると「データクラスと、それに必要な機能を自動生成してくれるパッケージ」ということです。

Freezedの使い方

では実際にデータクラスを生成するまでの流れを、コードと合わせて説明します。

なお今回は「APIからjson形式のレスポンスを格納するデータクラスを作成」という
実際によくあるケースを元に実装を進めていきます。

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

まずは以下のURLのReadMeを元に、Freezedのパッケージをインストールしましょう。
https://pub.dev/packages/freezed/install

なお今回の実装で使うpubspec.yamlの記述は以下になります。
※記述後は"pub get"を行い、パッケージをインストールすること

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  # freezed
  freezed_annotation: 2.2.0


dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0
  # freezed
  # 開発時にしか使用しないので、「dev_dependencies」に記載
  freezed: 2.2.1
  build_runner: 2.3.2
  # json形式のレスポンスを受け取るので、変換用に「json_serializable」もインストール
  json_serializable: 6.5.4

2. クラスを作成

インストールが終わりましたら、次はデータクラスを作成していきましょう。

今回はUserのデータを受け取るためのデータクラスを作ります。
以下が実装コードです。

また以下のコードを記述すると、最初はエラーが出ますが
コード生成後は出なくなるので気にせずOKです。

user.dart

///////// a. パッケージをインポートし、自動生成されるファイルを記述 /////////
import 'package:freezed_annotation/freezed_annotation.dart';

// 生成されるdartファイルを記述
part 'user.freezed.dart';
part 'user.g.dart';

///////// b. コード生成するためのクラスを作成 /////////

// freezedでコード生成するために「@freezed」を記述

class User with _$User { // withの後には「_$[class name]」の形式で記述

  // プロパティを指定
  const factory User({
    required int id,
    required String username,
    required String email,
    required String phone,
    (false) bool isPremium, // デフォルト値は「@Default([デフォルト値]])」の形式で指定可能
  }) = _User;

  // json形式で受け取るためのコードを生成するために記述
  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}

ではコメントの内容について、1つづつ解説します。

a. パッケージをインポートし、自動生成されるファイルを記述

まずはFreezedのアノテーションを使うために、以下のインポート文を記述しましょう。

 import 'package:freezed_annotation/freezed_annotation.dart'; 

次はpartキーワードの後ろに、この後コード生成されるdartファイルを記載します。

part 'user.freezed.dart';
part 'user.g.dart';

各ファイルの役割は以下になります。

  • [File Name].freezed.dart → Freezedで生成されるデータクラス
  • [File Name].g.dart → jsonを変換する処理が生成されるクラス

なお「partって何?」と思われた方もいると思います。

参考記事のStack Overflowでは、以下のように回答されてます。

Dartでは、プライベートメンバーは同じライブラリ内でアクセス可能です。
importを使用すると、ライブラリをインポートし、そのパブリックメンバにのみアクセスすることができます。

part/partofを使用すると、1つのライブラリを複数のファイルに分割し、それらのファイル内のすべてのコードでプライベートメンバーにアクセスすることができます。

importと比べると、partはプライペートなメンバーへアクセスできるようになるキーワードということみたいです。

b. コード生成するためのクラスを作成

クラスの中身を作成していきましょう。

まずは以下のようにクラスを作成します。


class User with _$User {}

ここでの実装ポイントは以下です。

  • freezedでコード生成するために「@freezed」を記述
  • withの後には「_$[class name]」の形式で記述

次にユーザー情報に必要なプロパティを指定していきましょう。

  const factory User({
    required int id,
    required String username,
    required String email,
    required String phone,
    (false) bool isPremium,
  }) = _User;

ここでの実装ポイントは以下です。

  • デフォルト値は「@Default([value]])」の形式で指定可能

最後にjson形式で受け取るためのコードを生成するために、以下の一文を追加しましょう。

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

3. コードを生成

これで作成に必要な準備は整いました。
最後に以下のコマンドをプロジェクト直下で実行しましょう。

flutter pub run build_runner build

※既にファイルが存在し、再生成する場合は以下のコマンドを使用(更新の場合など)

flutter pub run build_runner build --delete-conflicting-outputs

うまくいけば、以下のようにコードが自動生成されます。

Freezedで生成したデータクラスの使用方法

データクラスは作成できましたが、「実際の使い方はどうなの?」と思われる方も多いでしょう。

そこで作成したデータクラスを実際どのように使うかの実装例を見ていきましょう。

要件は以下です。

  1. json形式のユーザー情報をデータクラスに格納
  2. 格納したデータクラスを元に、画面へユーザー情報を表示

では早速実装していきましょう。

1. json形式のユーザー情報をデータクラスに格納

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  # http(API通信用パッケージ)
  http: ^0.13.5
  # freezed
  freezed_annotation: 2.2.0

user_request.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../model/user.dart';

class UserRequest {
  // jsonplaceholderのapiを使用
  Uri url = Uri.parse('https://jsonplaceholder.typicode.com/users');

  Future<List<User>> getAllUser() async {
    // user情報を取得する
    http.Response response = await http.get(url);

    if (response.statusCode != 200) {
      return <User>[];
    }

    // 受け取ったStringのjsonレスポンスを、List形式に変換
    List userMap = jsonDecode(response.body);

    List<User> users = [];
    userMap.forEach((user) {
      // 変換したレスポンスを、Freezedで作成したUserクラスの中に入れる
      // 入れる際には、fromJson(json形式の値を、Userクラスのプロパティへ代入)を使用する
      users.add(User.fromJson(user));
    });

    // UserクラスのListを返却
    return users;
  }
}

2. 格納したデータクラスを元に、画面へユーザー情報を表示

freezed_user_page.dart

import 'package:flutter/material.dart';
import 'package:freezed_example/api/user_request.dart';
import 'package:freezed_example/model/user.dart';

class FreezedUserPage extends StatelessWidget {
  const FreezedUserPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    final userRequest = UserRequest();
    List<User> userList = <User>[];

    return Scaffold(
      body: FutureBuilder<List<User>>(
        // ①で作成した関数を実行
        // ※実行が終わったタイミングでbuilderが実行
        future: userRequest.getAllUser(),
        builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
          // ①の実行結果(snapshot.data)を、userListの変数に代入
          userList = snapshot.data ?? <User>[];
          return ListView.builder(
            itemCount: userList.length,
            itemBuilder: (BuildContext context, int index) {
              return Card(
                shadowColor: Colors.black,
                child: Column(
                  // userListに入っている各値を表示する
                  children: [
                    SizedBox(
                      height: 10,
                    ),
                    // UserName
                    Row(
                      children: [
                        Text(userList[index].id.toString() + ':'),
                        Text(userList[index].username),
                      ],
                    ),
                    SizedBox(
                      height: 10,
                    ),
                    Row(
                      // Email
                      children: [
                        Text('Email:'),
                        Text(userList[index].email),
                      ],
                    ),
                    Row(
                      // Phone
                      children: [
                        Text('Phone:'),
                        Text(userList[index].phone),
                      ],
                    ),
                  ],
                ),
              );
            },
          );
        },
      ),
    );
  }
}

まとめ

いかがでしたでしょうか。
Freezedを使えば最低限のソースコードで、安全・機能性の高いデータクラスを作成できることがわかったと思います。

みなさんもFreezedを活用し、素敵なFlutterライフをお過ごしくださいませ。では〜。

Discussion