💊

【Flutter】DartでResult型を使う

に公開

要約

result_dart は、成功と失敗を明示的に返す「Result」をDartで使えるようにするパッケージです。これにより、try/catch の散在や「どこで例外が出るのか分かりにくい」問題を軽減し、UI・状態管理層で明示的にハンドリングできます。Kotlin、SwiftのResultに着想を得ており、Dart3のパターンマッチングとも相性が良いです。

対象

  • FlutterやDartを使い始めた初心者
  • try/catch文で例外を処理するのではなく、メソッド単位で明示的に失敗処理をする方向に切り替えたい人

問題設定

Dartの例外は宣言的ではありません。関数が例外を投げるかどうかは式からは分からず、呼び出し側に try/catch を書き忘れると未処理例外→クラッシュを招きます。結果として、データ層からリポジトリ、ViewModelやControllerの各レイヤーで例外の伝播が不透明になります。公式ドキュメントも、こうした課題への対策として Resultパターンの有用性を解説しています。

解決アプローチ

基本的な使い方

戻り値をSuccess()、Failure()で囲ってreturnします。

Result<String> getSomethingPretty() {
    if(isOk) {
        return Success('OK!');
    } else {
        return Failure(Exception('Not Ok!'));
    }
}

戻り値は拡張メソッドを使うこともできます。
ただ、この拡張メソッドはFuture型やResult型には使えないため、その点だけ注意してください。

Result<String> getSomethingPretty() {
    final isOk = Random().nextBool();
    if(isOk) {
        return 'OK!'.toSuccess();
    } else {
        return Exception('Not Ok!').toFailure();
    }
}

値の取得方法は以下のように、パターンで取得できます。

  Future<void> loadUser(int id) async {
    final result = await _repo.fetchUser(id);
    switch (result) {
      case Success(value: final data):
        print('User loaded: ${data['name']}');
      case Failure(exception: final e):
        print('Failed: $e');
    }
  }

取得方法は他にもあり、fold()を使うと、Failureの際には加工した値を取得したいということもできます。

void main() {
    final result = getSomethingPretty();
     final String message = result.fold(
        (success) {
          // handle the success here
          return "success";
        },
         (failure) {
          // handle the failure here
          return "failure";
        },
    );
}

値の取得する際のutilityメソッドがいくつか用意されているので、気になる方はresult_dartのドキュメントを確認してみてください。

まとめ

例外を「値」に変えることで、失敗を型で表現し、明示的に実装することができます。toSuccess()やtoFailure()の拡張メソッドはStringやExceptionのような具体的な値に対して呼び出し、その値をResult型に変換するために使用します。Result、Future型には使用できないのは注意して使用してください。

Discussion