【Flutter】dioのInterceptorでAPI通信の共通処理を実装する

こんにちは。エキサイト株式会社 モバイルアプリエンジニアの克です。

今回は、FlutterにおけるdioのInterceptorを利用したAPI通信時の共通処理の実装についてお話しします。

各種バージョン

Flutter: 3.22.2
dio: 5.4.3+1

Interceptor

dioはFlutterでHTTP通信を行う際によく用いられるパッケージですが、その中でもInterceptorはリクエスト/レスポンス/エラーの際に処理を割り込ませるために用意されたものです。

Interceptorの使い方

Interceptorを使用する際には下記の2通りの方法があります。

  • Interceptorを継承したサブクラスを定義する
  • ヘルパークラスであるInterceptorsWrapperを使用する

Interceptorを継承して使用する

Interceptorを継承したサブクラスを作成します。 リクエスト/レスポンス/エラーがそれぞれonRequest/onResponse/onErrorとして定義されているため、必要に応じてメソッドをオーバーライドして使用します。 Interceptorを使い回す場合や、クラスとして管理したい場合はこちらのやり方がオススメです。

class CustomInterceptor extends Interceptor {
  const CustomInterceptor();

  @override
  void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
    ~~~
  }

  @override
  void onResponse(Response response, ResponseInterceptorHandler handler) {
    ~~~
  }

  @override
  void onError(DioException error, ErrorInterceptorHandler handler) {
    ~~~
  }
}

InterceptorsWrapperを使用する

InterceptorsWrapperはInterceptorを簡単に扱うためのヘルパークラスです。
自前でクラスとして定義する必要がないので、その場限りのInterceptorとしてシンプルに扱う場合に便利です。 その他の使い方に関しては継承した場合と同様となります。

final dio = Dio();
~~~
InterceptorsWrapper(
  onRequest: (options, handler) {
    ~~~
  },
  onResponse: (response, handler) {
    ~~~
  },
  onError: (error, handler) {
    ~~~
  },
);

Interceptorを実装する

次に、Interceptorをどのように実装するかについてです。 どのメソッドに対しても対応したInterceptorHandlerが渡されますので、このHandlerに対して以降の動作を指定する形となります。

  • onRequest -> RequestInterceptorHandler
  • onResponse -> ResponseInterceptorHandler
  • onError -> ErrorInterceptorHandler

どのHandlerにもnext, resolve, rejectの3つのメソッドがあり、いずれかのメソッドを呼び出します。

  • next: 通常通りに以降の処理を続行する
  • resolve: リクエストを成功扱いとする
  • reject: リクエストを失敗扱いとする

callFollowing〇〇Interceptorについて

Handlerによっては、各メソッド呼び出し時の引数に下記のフラグが存在する場合があります。

  • callFollowingResponseInterceptor
  • callFollowingErrorInterceptor

これは、対応する後続のInterceptorを呼び出すかどうかのフラグでありデフォルトはfalseです。
例えばRequestInterceptorHandlerでresolveを呼び出す際にcallFollowingResponseInterceptorのフラグをtrueに設定した場合は、リクエストの時点で値は返しつつもonResponseの処理を実行することになります。
基本的にはfalseで問題ないものですが、必ず実行しなければならない共通処理がある場合などはtrueにする必要があります。

dioにInterceptorを追加する

dioインスタンスに対してinterceptorsを参照し、addでInterceptorを設定します。
addAllで複数のInterceptorをまとめて設定することもできます。

final dio = Dio();
~~~
dio.interceptors.add(
  const HogeInterceptor(),
);

Interceptorの実行順序

dioに対しては複数のInterceptorを設定することができますが、実行されるInterceptorの順番は設定した順となります。
そのため、例えば通信のログを出力するようなInterceptorは最後に追加した方がいい場合があります。(最終的な通信内容を出力する必要があるため)

複数のリクエスト内での実行順序

上記は一つのリクエスト内での話ですが、複数のリクエストが同時に実行される場合には注意が必要です。
複数のリクエスト間ではInterceptorは同時に実行されることになるため、例えば「認証エラー時に認証トークンを取得して再実行」などを実装する際に問題になります。
認証トークンを取得している間に別のリクエストが実行された場合、認証トークンの取得を待たずにInterceptorが実行されるためそちらでも認証トークンの取得が走ってしまうためです。

こういったケースではQueuedInterceptorsWrapperを使用することで解決することができます。
QueuedInterceptorsWrapperはリクエスト間のキューを管理しているため、同時に複数のリクエストが実行された場合でも実行中のInterceptorの完了を待ちます。
逆に排他制御が不要な処理に対して使用すると無駄な待機時間が発生してしまうため気をつける必要があります。

まとめ

Interceptorを活用することで、API通信時の共通処理の実装や共通パラメータの付与、カスタムレスポンスの取り回しなどが格段にやりやすくなります。
通信周りの処理はモバイルアプリでも不安定になりやすい部分なので、Interceptorをうまく活用してより堅牢なアプリにしていきましょう。

採用情報

エキサイトでは各種エンジニアを募集しております。
興味があれば是非ご連絡いただければと思います!

募集職種一覧はこちら www.wantedly.com