Flutterアプリのネットワークデバッグを強化するchuck_interceptorの使い方

ドライバー用アプリ『GOドライバー』を開発しているFlutterエンジニアの井戸田です。
本記事では、『GOドライバー』に chuck_interceptor を導入して得られた知見を紹介します

chuck_interceptorを導入した背景

私たちの開発チームでは、アプリケーションのネットワーク通信に起因する処理の不具合に悩んでいました。
不具合が発生した際、その原因がアプリ側にあるのか、API側にあるのかを迅速かつ正確に判断するのが困難でした。例えば社内の方から「特定の機能が正しく動作しない」という報告があった場合、 その問題がFlutterのコードによるものなのか 、またはAPIからのレスポンス に原因があるものなのか特定するために、詳細にヒアリングする必要がありました。 問題の再現方法を特定するために何度もやり取りを繰り返すことで、時間を費やしおり、コミュニケーションコストを増大させていました。

そのためchuck_intercepterを導入しようと決めました。

chuck_interceptorとは

https://pub.dev/packages/chuck_interceptor

chuck_interceptorはAndroidライブラリであるChuckとChuckerからインスパイヤーされたFlutterのライブラリで、ネットワークリクエストをデバッグするためのツールです。
このライブラリは、HTTPリクエストとレスポンスをインターセプトし、詳細な情報を提供します。API通信の問題の特定と解決を行うために役立ちます。

chuck_interceptorを使うことで次のような利点があります。

  • HTTPリクエストとレスポンスの可視化
  • ステータスコード、ヘッダー、ボディなどの詳細情報の取得
  • リクエストのパフォーマンス測定
  • エラーの迅速な発見と修正

またFlutter内で完結できるので、非エンジニアの方でも確認がしやすいと感じています。

導入方法

今回はDioを使った方法で紹介します。
他にも dart:io パッケージである HttpClient や、 httpChopper での導入方法もREADMEに記載されているので、ご覧ください。

パッケージのインストール方法

chuck_interceptorとdio以外にも、 依存性の解決のためにflutter_riverpodも入れておきます。

dependencies:
  flutter:
    sdk: flutter
  chuck_interceptor: ^2.1.6
  dio: ^5.4.3+1
  flutter_riverpod: ^2.5.1

Chuckを定義

final chuckProvider = Provider<Chuck>(
  (ref) => Chuck(),
);

今回はオプションを指定していませんが、Chuckにはさまざまなオプションがあり必要に応じて設定できます。

オプション名 default 詳細
showNotification true リクエストをキャッチした際に通知するか。
中ではflutter_local_notificationsが使われているようです。
showInspectorOnShake false バイスの揺れでインスペクターページを開くか(センサー付きの物理的なものでのみ機能する)
darkTheme false インスペクターページをdarkテーマで表示させるか
notificationIcon @mipmap/ic_launcher 通知のアイコン
maxCallsCount 100 保存するコール数
directionality null アプリのDirectionality。nullに設定されている場合は、アプリのDirectionalityを使用。

インターセプターを設定

final dioProvider = Provider((ref) {
  final options = BaseOptions(
    baseUrl: 'http://XXX',
    headers: {
      Headers.contentTypeHeader: Headers.jsonContentType,
    },
  );

  return Dio(options)
    ..interceptors.add(
      ref.read(chuckProvider).getDioInterceptor(),
    );
});

diointerceptors にchuck_interceptorの getDioInterceptor を追加します。

ナビゲーションキーを設定

MaterialApp(
  // ...
  navigatorKey: ref.read(chuckProvider).getNavigatorKey(),
  // ...
);

インスペクターページを表示させるためにナビゲーションキーを設定します。

設定は以上です。実際に動かしてみます。

実際に動かしてみる

dioAPIをリクエスト後に chuck.showInspector(); を呼び出すか、またはChuckクラスの showInspectorOnShaketrue の場合は端末を振った場合、下記のようなインスペクターページが表示されます。

一覧

リクエストしたAPIの一覧が表示され、各アイテムとしてはエンドポイント、HTTPステータス、通信時間、応答速度、送受信したデータサイズが表示されます。

詳細

Overview Request Response Preview Error
  • Overview
    • Methodやエンドポイント、リクエストした時間、終わった時間等が表示されます
  • Request
    • リクエストの詳細情報(リクエストした時間、bodyの情報、headerの情報、クエリーパラメータ等)が表示されます。
    • 今回は「Body is empty」になっていますが、POSTなどでbodyが空ではない場合は表示されます。
    • Headersにはdioで設定したheader情報をすべて表示されるため、AccessTokenやその他機密情報など表示されている場合は、シェア時に注意する必要があるかもしれません。
  • Response
    • レスポンスの詳細情報(返ってきた時間、HTTPステータス、headerの情報、body情報)が表示されます
    • こちらもレスポンスの情報全て表示されるため機密情報等シェアしたくない情報が表示されている場合は、注意する必要があるかもしれません。
  • Preview
    • レスポンスのbody情報が表示されます
  • Error
    • DioExceptionなどExceptionが発生した場合、そのエラー情報が表示されます

またFloatingActionButtonをタップすることで、この情報をシェアすることができます。
(内部ではshare_plusを使っています。)

出力はテキスト形式で、下記のような形です。

Chuck - HTTP Inspector
App name:  Chucksample
Package: [パッケージ名]
Version: 1.0.0
Build number: 1
Generated: 2024-05-26T01:08:21.647134
===========================================
Id: 856632419
============================================
--------------------------------------------
General data
--------------------------------------------
Server: localhost
Method: GET
Endpoint: /api/v1/articles/search?q=title&created_at=2024/01/01
Client: Dio
Duration 74 ms
Secured connection: false
Completed: true
--------------------------------------------
Request
--------------------------------------------
Request time: 2024-05-26 01:06:27.617510
Request content type: application/json
Request cookies: []
Request headers: {
  "content-type": "application/json"
}
Request size: 0 B
Request body: Body is empty
--------------------------------------------
Response
--------------------------------------------
Response time: 2024-05-26 01:06:27.691023
Response status: 200
Response size: 269 B
Response headers: {
  "connection": "[keep-alive]",
  "keep-alive": "[timeout=5]",
  "transfer-encoding": "[chunked]",
  "date": "[Sat, 25 May 2024 16:06:27 GMT]"
}
Response body: {
  "articles": [
    {
      "id": 1,
      "title": "title1",
      "description": "description1",
      "created_at": "2024/01/01"
    },
    {
      "id": 2,
      "title": "title2",
      "description": "description2",
      "created_at": "2024/01/01"
    }
  ]
}
--------------------------------------------
Curl
--------------------------------------------
curl -X GET -H 'content-type: application/json' 'http://localhost/api/v1/articles/search?q=title&created_at=2024/01/01'
==============================================

導入後の結果

chuck_interceptorを導入したことで、大きく2点改善されました。

1点目は原因を特定する速さです。
FlutterアプリケーションとAPIの間でやり取りされるすべてのHTTPリクエストとレスポンスを詳細に監視できるようになり、 「Flutter側、API側のどちらの問題なのか」 を迅速に特定することが可能となりました。 さらに、API側に問題がある場合、 「どの値が期待と異なるのか」 を正確に特定することができるようになりました。

2点目はコミュニケーションコストです。
不具合が発生した際には、どのような操作を行ったか確認した上で、chuck_intercepterのログを受け取るだけで済むことが多くなりました。
具体的には、以下の手順を踏むことで、問題を迅速に特定し解決策を見つけることができました。

  1. 不具合を発見した方から、開発者がインターセプターページの一覧のスクリーンショットを受け取る
  2. 開発側がそのスクリーンショットから問題のある可能性の高いAPIを推測し、不具合を発見した方にAPIログが欲しいと依頼する
  3. 不具合を発見した方から、開発側がそのAPIログを受け取る

その結果、コミュニケーションの回数が減り、情報の伝達がスムーズになりました。

chuck_interceptorの導入は、技術的な問題解決を容易にするだけでなく、チーム内外のコミュニケーションを改善し、開発プロセスを最適化するためのステップとなったと感じています。

最後に

chuck_interceptorを導入したことで、開発者以外の端末で発生した不具合でも原因の特定がしやすくなりました。
またコミュニケーションコストも削減にも公開がありました。

もし不具合が発生した際の問題の切り分けが難しいと感じている方がいらっしゃいましたら、導入も簡単にできますので、ご検討ください。
最後まで読んでいただきありがとうございました。