最近2匹の猫との生活を始めました、アプリエンジニアチームの務台です。
今回は今年中旬に正式リリースと噂のDart3を使って、みんな大好きTodoアプリを作成してみたので、その感想等を共有できればと思います。 なお、2023/03/17時点ではまだアルファ版のため、今後機能の変更や削除の可能性があります。
作成したアプリのソースはこちらです。
実行環境
開発時の注意
Dart3の機能はまだ正式リリースされていないため、コンパイル時に以下のオプションで明示的に指定する必要があります。
--enable-experiment=records,class-modifiers,sealed-class,patterns
また、lintや自動整形も効かなくなってしまうため注意が必要です。
新機能について
Dart3で追加される新機能については以下にまとめられています。
ここにあるように、Dart3の新機能には大きく以下のものがあります。
- record型の追加
- パターンマッチングの追加
- クラス修飾子の追加
それぞれの説明、使用した感想等についてまとめていきます。
record型
概要
複数の値をまとめたものを、簡単に新たな型として使うことができる機能です。
以下のような、天気、温度、湿度を取得する関数を作るとします。
[気候型] fetchClimate() { // 天気、温度、湿度を取得して返す }
従来では気候型のクラスを作って、それを返すようにする必要がありました。
@immutable class Climate { const Climate( this.weather, this.tempereture, this.humidity, ); final String weather; final double tempereture; final double humidity; } Climate fetchClimate() { return Climate('晴れ', 18.4, 70.4); }
record型を使うことでわざわざクラスを新設せず、以下のように簡易的に記述することができるようになりました。
(String weather, double temperature, double humidity) fetchClimate() { return ('晴れ', 18.4, 70.4); }
感想
かなり短く書くことができるようになりますが、個人的には基本的にrecord型は使わない方が良いかと思っています。 その理由として、record型は関数ごとに新たに返却するための型を宣言し直す必要があるため、同じデータを返すはずなのに型が違っている、という状況が起こり得ます。また、型の名前をつけることができないため、取得できるデータはなんなのかが推測しづらくなってしまいます。
…と思っているので、今回のサンプルアプリではrecord型は使用していません。スミマセン。
パターンマッチング
概要
パターンマッチングとは特定のパターンにマッチするデータを取得することができる機能です。
後述のsealed class
と組み合わせることにより、以下のように実際の型に合わせて処理を変える、ということができるようになります。
sealed class Shape {} class Square implements Shape { final double length; Square(this.length); } class Circle implements Shape { final double radius; Circle(this.radius); } double calculateArea(Shape shape) => switch (shape) { Square(length: var l) => l * l, Circle(radius: var r) => math.pi * r * r };
このような分岐は、既存ではFreezedを使用することで実現できていましたが、これでパッケージ無しで同様のことができるようになります。
特に嬉しいのがswitchを式として利用できるようになったことで、以前ではreturnを複数書かなければいけなかったものを簡潔に記述することができるようになりました。
感想
サンプルではTodoStatus
をsealed class
として用意して、Screen側で値によってアイコンを出し分ける、という処理を行っています。
lib/feature/todo/domain/value/todo_status.dart
sealed class TodoStatus{ String get statusString; } @immutable final class NotStarted implements TodoStatus{ @override String get statusString => 'NotStarted'; } @immutable final class InProgress implements TodoStatus{ @override String get statusString => 'InProgress'; } @immutable final class Finished implements TodoStatus{ @override String get statusString => 'Finished'; }
lib/feature/todo/presentation/todo_list/widget/todo_item.dart
// 27行目 アイコンの切り替え leading: Icon( switch(status) { NotStarted() => Icons.radio_button_unchecked, InProgress() => Icons.more_horiz, Finished() => Icons.radio_button_checked, }, color: Colors.grey, ), // 61行目 背景色の切り替え tileColor: switch(status) { NotStarted() => Colors.transparent, InProgress() => const Color.fromARGB(255, 207, 253, 209), Finished() => const Color.fromARGB(255, 207, 207, 207), },
ステータスによる表示の切り替えを簡潔に記述することができ、とても使い勝手が良かったです。
クラス修飾子の追加
概要
ここでのクラス修飾子とはclassの前につく修飾子のことで、今までDartにはabstract
くらいしか存在しませんでした。
Dart3からは一気に増えて、以下のキーワードが使用できるようになります。
宣言 | 説明 |
---|---|
interface class |
このクラスの継承を不可にする。他の言語でのinterfaceと同様だが、interface内でメソッドを実装する必要がある。 |
base class |
暗黙的インターフェースを無効にすることで、implementによる実装を無効にする。 |
final class |
このクラスの継承、実装、ミックスインを禁止する。 |
sealed class |
パターンマッチングで使用したように、複数のクラスを一つのグループとしてまとめる。 |
mixin class |
他クラスへのミックスインを有効化する |
感想
今までは暗黙的インターフェースにより意図しない継承、実装が可能だった点が微妙だと思っていたのですが、final
によりそれを封じることができるようになった点はとてもありがたいと感じました。プロダクト開発では基本的にfinal
を付けてクラス定義していくのが良さそうです。
interface
について、抽象メソッドを持つことができないため、UnimplementedError
を投げる等、不要な実装が必要になってしまうのが微妙かなと感じました。
lib/feature/todo/repository/todo_repository.dart
interface class TodoRepository { Future<int> add(domain.Todo todo) => throw UnimplementedError(); Future<int> update(domain.Todo todo) => throw UnimplementedError(); Future<void> delete(int id) => throw UnimplementedError(); Stream<List<domain.Todo>> watchAll() => throw UnimplementedError(); Future<domain.Todo> fetch(int id) => throw UnimplementedError(); }
ただし、2023/03/17現在ではinterface class
を継承してもコンパイルエラーにならなかったため、まだまだ変更、修正の可能性はあるのかなぁと思っています。
まとめ
個人的には暗黙的インターフェースの仕組みがあまり好きではなかったので、明示的に無効化できるようになってかなり便利だなぁと感じました。 その他の新機能についても、これまでFreezedが必要だった部分が言語としてサポートされて、今後に期待できそうです。
次はバリューオブジェクト型が増えるといいなぁ。