Flutter retrofit（dio）でログを出す


Flutterに限らずですが、retrofitを用いたAPI通信をする場合にログを出力しながら
デバッグしていきたい場面があると思います。
今回はサンプルとしてホットペッパーグルメAPIを用いながら解説していきます。

https://webservice.recruit.co.jp/doc/hotpepper/reference.html


ちなみに、retrofitは以下のようにAPI通信に関わるデータの変換処理を簡易的にしてくれるパッケージです。
`build_runner`を実行すると、`.g.dart`ファイルが生成され、めんどくさい変換処理を担ってくれます。
あくまで変換を簡易的にしてくれるもので、実際に通信を簡易的にしてくれるのは`dio`パッケージの役割です。


```dart
@RestApi()
abstract class HotpepperApiClient {
  factory HotpepperApiClient(AppDio dio, {String? baseUrl}) =
      _HotpepperApiClient;

  @GET('')
  Future<String> fetchHotpepperInfo({
    @Query('key') required String apiKey,
    @Query('format') required String format,
    @Query('lat') required double latitude,
    @Query('lng') required double longitude,
    @Query('range') required int range,
  });
}
```

（今回は`AppDio`がメインテーマになるので後ほど解説します）

https://pub.dev/packages/retrofit

https://pub.dev/packages/dio

使い方はこんな感じです。

```dart
final hotpepperClient = HotpepperApiClient(AppDio());
await hotpepperClient.fetchHotpepperInfo(
  apiKey: 'xxxxxx',
  format: 'json',
  latitude: 35.351532,
  longitude: 139.845848,
  range: 1,
);
```

このretrofit / dioはとても便利なのですが、実際にAPI通信のリクエストとレスポンスでどう言うデータを送受信しているかは設定しないと確認できません。以下で提示するように`DioMixin`を継承してカスタムすることが可能です。

```dart
import 'package:dio/dio.dart';

class AppDio extends DioMixin {
  factory AppDio() {
    final instance = _instance;
    if (instance != null) {
      return instance;
    }
    final dio = AppDio._();
    final options = BaseOptions(
      baseUrl: 'https://webservice.recruit.co.jp/hotpepper/gourmet/v1',
      connectTimeout: const Duration(seconds: 15),
      headers: {
        'accept': '*/*',
        'Accept-Language': 'ja',
        'X-Api-Version': '2.0',
        'Content-Type': 'application/json',
      },
      contentType: 'application/json',
    );
    dio
      ..options = options
      ..httpClientAdapter = HttpClientAdapter();
    dio.interceptors.add(
      InterceptorsWrapper(
        onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
          print('######## Request Log ########');
          print('--> ${options.method} ${options.path}');
          print('Full URL: ${options.uri}');
          print('Headers: ${options.headers}');
          print('Query Parameters: ${options.queryParameters}');
          print('Request Data: ${options.data}');
          return handler.next(options);
        },
        onResponse: (Response response, ResponseInterceptorHandler handler) {
          print('######## Response Log ########');
          print('<-- ${response.statusCode} ${response.requestOptions.path}');
          print('Headers: ${response.headers}');
          print('Response Data: ${response.data}');
          return handler.next(response);
        },
        onError: (DioException e, ErrorInterceptorHandler handler) {
          print('######## Error Log ########');
          print('<-- Error --> ${e.message}');
          return handler.next(e);
        },
      ),
    );
    _instance = dio;
    return dio;
  }

  AppDio._();

  static AppDio? _instance;
}
```

それを、冒頭のコードと同じになってしまいますがretrofitのコードに指定すればOKです

```dart
@RestApi()
abstract class HotpepperApiClient {
  factory HotpepperApiClient(AppDio dio, {String? baseUrl}) =
      _HotpepperApiClient;

  @GET('')
  Future<String> fetchHotpepperInfo({
    @Query('key') required String apiKey,
    @Query('format') required String format,
    @Query('lat') required double latitude,
    @Query('lng') required double longitude,
    @Query('range') required int range,
  });
}
```

サンプルなのでprint関数を用いていますが、おそらくlintで警告が出てしまうと思います。
そのため、適宜以下のようなログ用のパッケージを導入して置き換えてみてください。

https://pub.dev/packages/logger

https://pub.dev/packages/logging


ただし、Dioパッケージは便利なものでデフォルトでも`LogInterceptor`というクラスが用意されているためこちらを用いてもいいですね。
手動でログをカスタムする書き方を提示しまいしたが、ある程度のフォーマットであれば以下のような記述でも問題ないと思います。

```diff_dart
dio.interceptors.add(
-   InterceptorsWrapper(
-     onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
-       debugPrint('######## Request Log ########');
-       debugPrint('--> ${options.method} ${options.path}');
-       debugPrint('Full URL: ${options.uri}');
-       debugPrint('Headers: ${options.headers}');
-       debugPrint('Query Parameters: ${options.queryParameters}');
-       debugPrint('Request Data: ${options.data}');
-       return handler.next(options);
-     },
-     onResponse:
-         (Response<dynamic> response, ResponseInterceptorHandler handler) {
-       debugPrint('######## Response Log ########');
-       debugPrint(
-         '<-- ${response.statusCode} ${response.requestOptions.path}',
-       );
-       debugPrint('Headers: ${response.headers}');
-       debugPrint('Response Data: ${response.data}');
-       return handler.next(response);
-     },
-     onError: (DioException e, ErrorInterceptorHandler handler) {
-       debugPrint('######## Error Log ########');
-       debugPrint('<-- Error --> ${e.message}');
-       return handler.next(e);
-     },
-   ),
- );
+ dio.interceptors.add(LogInterceptor(
+   request: true,
+   requestHeader: true,
+   requestBody: true,
+   responseHeader: true,
+   responseBody: true,
+ ));
```