【Flutter】CancelableOperationで非同期処理(Future)を制御する方法

こんにちは、株式会社Pentagonでアプリ開発をしている髙谷です。

サーバなどからデータを非同期で取得する際に「途中でデータ取得のリクエストをキャンセルしたい」といったことがあるかもしれません。
このようなケースで活躍するのがCancelableOperationです。今回はこちらをご紹介します。

【こんな人に読んで欲しい】
・非同期処理の制御に困っているエンジニア
・CancelableOperationを使いこなしたいエンジニア

【この記事を読むメリット】
CancelableOperationの使用方法、使用するタイミングがわかり、非同期処理をより細かく制御できるようになります。

【結論】
CancelableOperationの生成とcancelメソッドの呼び出しにより、実行中の非同期処理をキャンセルし、新たに非同期処理を実行できます。ユーザーの操作を無駄なくリアルタイムに反映できるようになるでしょう。

目次

CancelableOperationとは

CancelableOperationは、リクエストなどの非同期処理(Future)を途中でキャンセルするクラスです。言い換えれば、CancelableOperationはキャンセル可能な非同期処理そのものといったところでしょう。
CancelableOperationを使うにあたって、パッケージ「async」(公式ドキュメントリンク)をインストールください。
キャンセル処理はCancelableOperationクラス(公式ドキュメントリンク)のcancel()メソッドによって実行されます。

CancelableOperationの使用方法

簡単な非同期処理を例に、実際にCancelableOperationを使用したサンプルコードや使用時の注意点をご紹介していきます。

サンプルコード

CancelableOperationを使ったサンプルコードを見ていきましょう。
非同期処理として、3秒ほどかかるint型のリスト取得のケースを考えていきます。
1回目のリスト取得を途中でキャンセルし、2回目のリスト取得に進むといった流れになっています。

import 'package:async/async.dart';

Future<void> main() async {
  final fetcher = Fetcher();
  // 1回目のリスト取得を実行
  fetcher.fetchList(mark: 1);
  // 2秒停止
  await Future<void>.delayed(const Duration(seconds: 2));
  // 2回目のリスト取得を実行
  fetcher.fetchList(mark: 2);
}

class Fetcher {
  CancelableOperation? _cancelableOperation;

  Future<void> fetchList({required int mark}) async {
    /// CancelableOperationの実行をキャンセル
    await _cancelableOperation?.cancel().then((_) {
      // キャンセル結果の処理
      print('${mark - 1}回目のリスト取得をキャンセル');
    }, onError: (error, stacktrace) {
      // キャンセル時のエラーハンドリング
      print('エラーが発生しました');
    });

    /// CancelableOperationを生成
    print('$mark回目のリスト取得を開始');
    _cancelableOperation = CancelableOperation.fromFuture(
      // リストを取得
      _createList(mark: mark),
    ).then((value) {
      // リスト取得結果の処理
      print('$mark回目のリスト取得を終了');
      print('$mark回目のリスト取得結果$value');
    }, onError: (error, stacktrace) {
      // リスト取得時のエラーハンドリング
      print('エラーが発生しました');
    });
  }

  Future<List<int>> _createList({required int mark}) async {
    // 3秒停止
    await Future<void>.delayed(const Duration(seconds: 3));
    // リストを生成
    final list = List.generate(5, (index) => index);
    return list;
  }
}

こちらのコードを実行した結果が以下です。

flutter: 1回目のリスト取得を開始
flutter: 1回目のリスト取得をキャンセル
flutter: 2回目のリスト取得を開始
flutter: 2回目のリスト取得を終了
flutter: 2回目のリスト取得結果[0, 1, 2, 3, 4]

処理の細かな説明はコード内のコメントの通りですが、特にfetchListメソッド内の処理を追っていきましょう。
以下の2つの処理によって非同期処理のキャンセル→新たな非同期処理の実行を行っているわけですね。

・cancelメソッドでCancelableOperationの実行をキャンセルする
・fromFutureメソッドでCancelableOperationを生成する

また、fromFutureメソッドの第一引数に非同期処理(Future)を渡しますが、今回は簡易的なリスト生成(_createListメソッド)にしています。本来であれば、この第一引数の非同期処理(Future)内で、サーバなどからのデータ取得を行うといったケースが多いでしょう。

注意点

CancelableOperationを使う際の注意点です。cancelメソッドが実行された場合の挙動について、以下の点をおさえておくといいでしょう。

・cancelメソッドの実行によってthenメソッド以降が実行されなくなる
・cancelメソッドが実行されたとしても、fromFutureメソッドに渡した非同期処理(今回でいう_createListメソッド)は最後まで実行される
・fromFutureメソッドに渡した非同期処理が未完の場合のみキャンセルされ、完了済の場合はキャンセルされない

CancelableOperationを使用するタイミング

非同期処理(Future)のキャンセルを柔軟にできることがメリットですが、主に以下のようなケースで役立つかと思います。

・非同期処理(サーバへのリクエストなど)を途中でキャンセルし、無駄な処理を発生させない
・何らかの理由で非同期処理が完了しない場合に強制的にキャンセルしたい

例えば、ユーザーがリスト形式のデータを取得するケースを考えてみましょう。初めに昇順でのリスト取得のリクエストをサーバーに送りますが、その直後にUI操作で降順取得に変更したとします。
この場合、初めに送った昇順取得のリクエストは不要になりますよね。このようなケースがCancelableOperationの出番です。

まとめ

CancelableOperationを適切にキャンセル・再実行することで、非同期処理をより細かく制御できました。
CancelableOperationを使いこなすことで、ユーザーの使用性のさらなる向上につながるかもしれませんね。ぜひアプリの改善に使ってみてください!

おまけ

Flutterに関する知識を深めたい方には、『Flutterの特徴・メリット・デメリットを徹底解説』という記事がおすすめです。

この記事では、Flutter アプリ開発の基本から、Flutter とは何か、そして実際のFlutter アプリ 事例を通じて、その将来性やメリット、デメリットまで詳しく解説しています。
Flutterを使ったアプリ開発に興味がある方、またはその潜在的な可能性を理解したい方にとって、必見の内容となっています。

ぜひ一度ご覧ください。

採用情報はこちら
目次