👻

FlutterでHTTPリクエストのリトライ処理を作る

2024/01/17に公開

はじめに

dioとは、pub.devでLIKES 6000を超える超人気のAPI通信を行うためのパッケージです。

https://pub.dev/packages/dio

API通信を構築する際に、リトライ処理を書きたいケースは珍しくないですね。

その場合、サードパーティパッケージを探して利用するのは勿論、細かいところまでカスタマイズしたいが、外部パッケージだとやりづらいと感じて自前で実装することも全然ありかと思います。

この記事では、Interceptorを使ったリトライ処理を実装する方法を紹介したいと思います。

利用パッケージ

2024/1/17現在最新のバージョンをインストールします。

pubspec.yaml
dependencies:
  dio: ^5.4.0

実装方法

void main() {
  final dio = Dio();
  dio.interceptors.add(RetryInterceptor(dio));

  // API通信を行う
  dio.get('https://api.example.com');
}

class RetryInterceptor extends Interceptor {
  RetryInterceptor(this.dio);

  final Dio dio;

  static const _retries = 3;
  static const _retryInterval = Duration(seconds: 1);
  static const _extraKey = 'dio_retries';

  
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    // ここでは、リクエストをサーバーに送る前に何かの処理を行う
    // 例:アクセストークンの検証と再取得
    super.onRequest(options, handler);
  }

  
  Future<void> onError(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final statusCode = err.response?.statusCode;
    final isUnauthorized = statusCode == HttpStatus.unauthorized;

    // 何かエラーが発生したらリトライを行う
    if (needRetry) {
      await _retry(err, handler);
      return;
    }

    return super.onError(err, handler);
  }

  Future<void> _retry(
    DioException err,
    ErrorInterceptorHandler handler,
  ) async {
    final extra = err.requestOptions.extra;
    final retries = err.requestOptions.retries;
    if (retries <= 0) {
      // リトライ上限回数に達したので、エラーを返す
      return super.onError(err, handler);
    }

    // リトライ間隔を入れる
    await Future<void>.delayed(_retryInterval);

    try {
      // 再度APIを呼び出すなど、やりたいことをやる
      final response = await dio.fetch<dynamic>(err.requestOptions);
      // リトライ回数をリセットし、レスポンスを返す
      extra.remove(_extraKey);
      return handler.resolve(response);
    } catch (_) {
      // リトライ回数を1ずつ減らしてリクエストに設定する
      err.requestOptions.extra = extra..addAll({_extraKey: retries - 1});
      // リトライ処理を再帰的に実行する
      _retry(err, handler);
    }
  }
}

extension _RequestOptionsHelper on RequestOptions {
  /// キャッシュしたリトライ回数を取得する
  int get retries {
    final retryExtra = extra[RetryInterceptor._extraKey];
    if (retryExtra is int) {
      return retryExtra;
    }
    return RetryInterceptor._retries;
  }
}

さいごに

Interceptorを継承して独自のリトライ処理を実装する方法を紹介しました。
エラーハンドリングや認証処理などでよく使われている機能なので、個人開発はもちろん、普段の業務にもご活用いただければ幸いです。

GitHubで編集を提案

Discussion