🎯
APIレスポンスの型安全な処理方法
概要
APIレスポンスの形式が異なる場合の処理方法について、Dart 3.0の機能を使用した2つのアプローチを説明します:
- sealed classを使用したアプローチ
- enumを使用したアプローチ
sealed classを使用したアプローチ
こちらがサンプル
// APIレスポンスの異なる形式を表現するための例
// 1. sealed classを使用した例
sealed class ApiResponse {
const ApiResponse();
}
// 成功レスポンス(データあり)
final class SuccessWithData extends ApiResponse {
final Map<String, dynamic> data;
const SuccessWithData(this.data);
}
// 成功レスポンス(メッセージのみ)
final class SuccessWithMessage extends ApiResponse {
final String message;
const SuccessWithMessage(this.message);
}
// エラーレスポンス
final class ErrorResponse extends ApiResponse {
final String error;
final int code;
const ErrorResponse(this.error, this.code);
}
// 2. enumを使用した例
enum ResponseType {
success,
error,
loading;
// enumの拡張メソッド
String get message {
return switch (this) {
ResponseType.success => '成功しました',
ResponseType.error => 'エラーが発生しました',
ResponseType.loading => '読み込み中...',
};
}
}
// APIレスポンスを処理する関数例
void handleApiResponse(ApiResponse response) {
// sealed classを使用したパターンマッチング
final result = switch (response) {
SuccessWithData(data: final data) => 'データ: $data',
SuccessWithMessage(message: final msg) => 'メッセージ: $msg',
ErrorResponse(error: final err, code: final code) => 'エラー: $err (コード: $code)',
};
print(result);
}
void main() {
print('=== sealed classの例 ===');
// 異なる形式のAPIレスポンスをシミュレート
final responses = [
SuccessWithData({'id': 1, 'name': 'テスト'}),
SuccessWithMessage('処理が完了しました'),
ErrorResponse('不正なリクエスト', 400),
];
// 各レスポンスを処理
for (final response in responses) {
handleApiResponse(response);
}
print('\n=== enumの例 ===');
// enumを使用した状態管理
for (final type in ResponseType.values) {
print('${type.name}: ${type.message}');
}
// enumを使用した分岐処理
final status = ResponseType.success;
final statusMessage = switch (status) {
ResponseType.success => '✅ 成功',
ResponseType.error => '❌ エラー',
ResponseType.loading => '⏳ 読み込み中',
};
print('\n現在のステータス: $statusMessage');
}
実行結果:
=== sealed classの例 ===
データ: {id: 1, name: テスト}
メッセージ: 処理が完了しました
エラー: 不正なリクエスト (コード: 400)
=== enumの例 ===
success: 成功しました
error: エラーが発生しました
loading: 読み込み中...
現在のステータス: ✅ 成功
Exited.
特徴
- 異なる形式のデータを型安全に扱える
- パターンマッチングで網羅性チェックが可能
- データを持つことができる
- 継承関係を表現できる
実装例
sealed class ApiResponse {
const ApiResponse();
}
final class SuccessWithData extends ApiResponse {
final Map<String, dynamic> data;
const SuccessWithData(this.data);
}
final class SuccessWithMessage extends ApiResponse {
final String message;
const SuccessWithMessage(this.message);
}
final class ErrorResponse extends ApiResponse {
final String error;
final int code;
const ErrorResponse(this.error, this.code);
}
パターンマッチングの使用
final result = switch (response) {
SuccessWithData(data: final data) => 'データ: $data',
SuccessWithMessage(message: final msg) => 'メッセージ: $msg',
ErrorResponse(error: final err, code: final code) =>
'エラー: $err (コード: $code)',
};
enumを使用したアプローチ
特徴
- シンプルな状態管理に適している
- 限られた値の集合を表現できる
- メソッドや計算プロパティを持てる
- データを持つことはできない(Dart 3.0現在)
実装例
enum ResponseType {
success,
error,
loading;
String get message {
return switch (this) {
ResponseType.success => '成功しました',
ResponseType.error => 'エラーが発生しました',
ResponseType.loading => '読み込み中...',
};
}
}
使い分け
sealed classを使う場合
- 異なる形式のデータを持つ必要がある場合
- 複雑な状態や振る舞いを表現する必要がある場合
- 型の安全性を最大限確保したい場合
enumを使う場合
- 単純な状態管理が目的の場合
- 限られた値の集合を表現する場合
- データを持つ必要がない場合
ベストプラクティス
sealed class
- 基底クラスはsealedで定義し、サブクラスはfinalで定義する
- constコンストラクタを使用してイミュータブルにする
- パターンマッチングで全てのケースを網羅する
enum
- 関連する定数の集合を表現する
- 拡張メソッドを活用して振る舞いを追加する
- switchステートメントで全てのケースを網羅する
エラーハンドリング例
try {
handleApiResponse(response);
} on Exception catch (e) {
print('エラーが発生しました: $e');
}
パフォーマンスの考慮事項
- sealed class: インスタンス生成のオーバーヘッドあり
- enum: 単純な値の比較のみなので高速
Dart 3.0の新機能活用
- パターンマッチング
- 網羅性チェック
- recordとの組み合わせ
- switchの式としての使用
Discussion