【Flutter】マクロ対応のデータクラスを作った

こんにちは。広瀬マサルです。

Google I/O 2024でFlutterのマクロの発表がありましたね。

Masamuneフレームワークは「**アプリ開発のすべてをマクロでサポートする**」ことを最終的な目標として開発を続けていました。

マクロへの対応を行うためにこれまで[freezed](https://pub.dev/packages/freezed)や[json_serializable](https://pub.dev/packages/json_serializable)に頼っていたデータクラスやJsonの変換をMasamuneフレームワーク内で行うことにしました。

使い方をまとめたので興味ある方はぜひ使ってみてください！

```
💡 2024年6月時点ではDartのマクロ機能はプレビュー版です。
破壊的な変更が続くことが予想されるため当パッケージも仕様が大幅に変更される可能性が高いです。ご注意ください。
```

# katana_value

https://pub.dev/packages/katana_value

# はじめに

Dartのマクロ機能を利用しイミュータブルなデータクラスの作成を可能にします。

`比較演算子`への対応および`copyWith`の実装、`toString`の整備がアノテーションを付与するだけで可能になります。

また合わせて`Jsonからの変換`、`Jsonへの変換`も可能にします。

下記のように簡単に定義可能です。

```dart
@DataValue
class Person {
	Person({
	  required String name,
	  int? age,
	});
}
```

下記のように利用することが可能です。

```dart
final person = Person(name: "Kanimiso");
final copied = person.copyWith(age: 20);
final json = person.toJson();
print(json); // {name: "Kanimiso", age: 20}
```

# 事前準備

```
💡 2024年6月時点で必要な準備です。マクロがStableになった場合必要ありません。
```

1. [Dart dev channel](https://dart.dev/get-dart#release-channels) もしくは [Flutter master channel](https://docs.flutter.dev/release/upgrade#other-channels) に変更します。
2. `dart --version`を実行しDartのバージョンを`3.5.0-152`より新しくします。
3. `pubspec.yaml`のDartSDKのバージョンを`^3.5.0-152`に設定します。
4. `analysis_options.yaml`に下記のコードを追加します。
    
    ```dart
    analyzer:
     enable-experiment:
       - macros
    ```
    
5. `dart run --enable-experiment=macros bin/my_app.dart`

# インストール

下記パッケージをインポートします。

```dart
flutter pub add katana_value
```

# 実装

## データクラスの作成

`@DataValue`のアノテーションをデータクラスにしたいクラスに付与します。

コンストラクタに必要なフィールドの型と名前を定義します。これだけでデータクラスが作成されます。

```dart
@DataValue
class Person {
	Person({
	  required String name,
	  int? age,
	});
}
```

## データクラスの利用

定義したクラスはそのままオブジェクト化を行うことができます。

```dart
final person = Person(name: "Kanimiso", age: 20);
```

また`copyWith`や`toJson`、`fromJson`などのメソッドを利用することが可能です。

```dart
final json = {"name": "Kanimiso", "age": 20};
final person = Person.fromJson(json);
print(person.toString()); // Person(name: Kanimiso, age: 20)

final copied = person.copyWith(age: 30);
print(copied.toJson()); // {"name": "Kanimiso", "age": 30};
```

## Jsonに変換可能な型

初期状態では下記の型が変換可能です。（Nullable含む）

- `int`
- `double`
- `num`
- `String`
- `bool`
- `Iterable`
- `List`
- `Set`
- `Map`

### 新しいJson変換型への対応

上記の型が初期で利用可能ですが新しくJsonの変換に対応したい型がある場合は`DataValueJsonConverter`を利用します。

1. `DataValueJsonConverter`を継承したクラスを作成します。
    
    ```dart
    class ModelCounterJsonConverter extends DataValueJsonConverter {
    	const ModelCounterJsonConverter();
    	
    	@override
      Map<String, dynamic>? toJson(dynamic value) {
        if (value is ModelCounter) {
          return value.toJson();
        }
        return null;
      }
    
      @override
      dynamic fromJson(String key, Map<String, dynamic> json) {
        final map = json[key];
        if(map is Map<String, Object?> && map["#type"] == "ModelCounter") {
          return PersonBase.fromJson(map);
        }
        return null;
      }
    }
    ```
    
2. 作成したクラスをmainメソッド内などアプリ起動時に`DataValueJsonConverter.register`で登録します。
    
    ```dart
    void main(){
      DataValueJsonConverter.register(const ModelCounterJsonConverter());
    }
    ```
    

# 実行

```
💡 2024年6月時点で必要な準備です。マクロがStableになった場合必要ありません。
```

実行には下記のコマンドを利用します。

```bash
dart run --enable-experiment=macros bin/my_app.dart
```

# おわりに

自分で使う用途で作ったものですが実装の思想的に合ってそうならぜひぜひ使ってみてください！

また、[こちら](https://github.com/mathrunet/flutter_masamune)にソースを公開しているのでissueやPullRequestをお待ちしてます！

また仕事の依頼等ございましたら、私の[Twitter](https://twitter.com/mathru)や[Webサイト](https://mathru.net)で直接ご連絡をお願いいたします！

https://mathru.net/ja/contact

# GitHub Sponsors

スポンサーを随時募集してます。ご支援お待ちしております！

https://github.com/sponsors/mathrunet