こんにちは!FlutterやDartを使った開発を楽しんでいますか?
他のプログラミング言語、例えばJavaやTypeScript、Swiftなどを触ってきた方なら、「状態管理」の複雑さに頭を悩ませた経験が一度はあるはずです。
「あれ、この画面の状態、エラーの場合の処理を書き忘れてないっけ?」
「新しい状態を追加したのに、どこかでハンドリング漏れが起きて実行時エラー(クラッシュ)になった……」
そんな不安を、Dart 3の最強コンパイラに一発で解決してもらう方法があるんです。それが、`sealed class`(シールドクラス)とパターンマッチング(変数宣言時の網羅性チェック)を組み合わせたモダンな設計手法です。
ここをクリアすれば、あなたの書くDartコードの堅牢性は圧倒的に跳ね上がります。さあ、一緒にDartの深淵を覗いてみましょう!
—
1. そもそも `sealed class` とは何か?
`sealed class` は、Dart 3で導入された非常に強力なクラス修飾子です。一言で言うと、「継承できる相手を、同じファイル内に完全に制限するクラス」です。
従来の `abstract class` だと、プログラムのどこからでも(別のファイルからでも)勝手に子クラスを作れてしまいました。そのため、コンパイラは「他にどんな子クラスが存在するか」をコンパイル時に把握できません。
しかし、`sealed` を使うと、コンパイラは「この階層構造の子クラスは、このファイルに書かれているものだけだ」と全知全能の神のように把握できます。この性質を利用するのが、Dart 3のパターンマッチングです。
図解:通常クラス vs Sealedクラス
【通常の抽象クラス (abstract)】
AppStatus (親)
├── Loading (同ファイル)
├── Success (別ファイルAで定義可能!)
└── Error (別ファイルBで定義可能! → コンパイラは全容を把握できない)
【Sealedクラス (sealed)】
AppStatus (親) [※同一ファイル内でのみ継承許可]
├── Loading (同ファイル)
├── Success (同ファイル)
└── Error (同ファイル)
→ コンパイラ「子クラスはこの3つだけ完全網羅されているな!」と確信できる
—
2. 実践!網羅性をコンパイラに強制する状態管理
それでは、実際のコードを見ていきましょう。
ここでは、よくある「非同期データの取得状態(Loading, Success, Error)」を `sealed class` で表現し、変数を宣言・初期化する際に網羅性チェックを効かせる例を実装します。
// status.dart
import ‘package:flutter/foundation.dart’;
/// 1. アプリの状態を定義する sealed class
sealed class AppStatus {}
class Loading extends AppStatus {}
class Success extends AppStatus {
final String data;
Success(this.data);
}
class Error extends AppStatus {
final String message;
Error(this.message);
}
void main() {
// 2. 現在の状態のインスタンスを生成(ここでは例として Success を代入)
AppStatus currentStatus = Success(‘Dart 3の極意をマスターしました!’);
// 3. switch式 (Switch Expression) を使ったパターンマッチング変数宣言
// ここが本記事のハイライトです!
final String messageWidget = switch (currentStatus) {
Loading() => ‘データを読み込んでいます…’,
Success(data: val) => ‘成功: $val’,
Error(message: err) => ‘エラーが発生しました: $err’,
// 💡もしここで Error() の処理を書き忘れると、コンパイルエラーになります!
};
debugPrint(messageWidget);
}
このコードの何がスゴいのか?
ここで使っている `switch (…)` は、Dart 3から導入された「Switch式 (Switch Expression)」という構文です。
右辺の値(ここでは `currentStatus`)をパターンマッチングし、一致した結果を変数 `messageWidget` に直接代入しています。
もし、あなたが後から仕様変更で `class Maintenance extends AppStatus {}` という新しい状態を追加したとします。
その瞬間、Dartのコンパイラは「おい! `Maintenance` の処理が書かれていないぞ!」と、アプリをビルド(コンパイル)する前に赤くエラーを出して教えてくれます。
実行時エラーに怯える日々とは、今日でお別れです。
—
3. 陥りやすい文法エラーと注意点
初学者の段階や、他の言語から移行したときによくやってしまうミスをいくつかピックアップしておきます。
落とし穴①:別のファイルで継承しようとして怒られる
// other_file.dart
import ‘status.dart’;
// ❌ コンパイルエラー!
// ‘AppStatus’ は別ファイルで宣言された sealed クラスのため、ここでサブクラス化できません。
class UnauthorizedAccess extends AppStatus {}
対策: `sealed class` とその子クラスは、必ず同一のファイル(ライブラリ)内に記述してください。状態のバリエーションを一箇所に閉じ込めるのが設計上の美しさでもあります。
落とし穴②:網羅性を崩してしまう「default」や「_」の乱用
スイッチ式を書く際、面倒だからといって `_ => ‘その他’` のようなワイルドカードを安易に置かないでください。
final result = switch (currentStatus) {
Loading() => ‘ロード中’,
_ => ‘その他’, // ❌ これを書いてしまうと、新しい状態を追加したときにコンパイラが警告してくれなくなります!
};
対策: 意図的にすべてのケースを明示的に書くことで、将来の改修漏れを防ぐという `sealed` の最大のメリットを活かせます。
—
先輩からの温かいアドバイス
お疲れ様でした!`sealed class` と変数宣言を組み合わせた網羅的な状態管理のイメージは掴めましたか?
「たかがコンパイルチェック」と侮るなかれ。大規模なFlutterアプリ開発やチーム開発において、「コンパイラが仕様の抜け漏れを物理的に防いでくれる」という安心感は、開発者のメンタル負荷を劇的に下げてくれます。
ここをクリアできれば、あなたのDartの基礎力は確かなものになっていますよ。自信を持って次のステップへ進んでいきましょう!質問があればいつでもコメント欄で聞いてくださいね。