🦇

dio + retrofit でサクサクAPIクライアント作成

2024/10/06に公開

記事の目的

dioとretrofitを用いてAPIクライアントを作成する方法を伝える。

前置き

flutterにはdioというpackageがあります。Http通信のpackageです。
https://pub.dev/packages/dio


このディオではありません

retrofitというpackageを利用することで、dioのhttpのクライアントのコード生成を自動化します。

https://pub.dev/packages/retrofit

本題

clientの作成 〜 フロントで呼び出すところまでを実装します。

あらかじめコード全体を共有します!
https://github.com/TakeRai/zenn_dio_retrofit_sample

Client 初期作成

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

part "api_client.g.dart";

final apiClient = ApiClient(Dio(_dioOption));

final _dioOption = BaseOptions(
  baseUrl: 'https://hoge-api-123456',
  contentType: 'application/json',
  headers: {
    'Authorization': 'Bearer hoge123fuga456', //実装に合わせて変更
  },
);

()
abstract class ApiClient {
  factory ApiClient(Dio dio) = _ApiClient;
}

まず簡単に、最低限のclient実装をしています。

ApiClientクラスは上記の記法のようにすることで自動生成されます。後ほど、クラス内に実際の関数を作成していきます。

API共通の設定はBaseOptionsで指定しています。

自動生成にはdioやretrofit以外に、以下のpackageも必要となります。

https://pub.dev/packages/retrofit_generator

https://pub.dev/packages/build_runner

自動生成を実行するためには、ターミナルで以下のコードを叩きます。freezedでお馴染みのコードです。

dart run build_runner build --delete-conflicting-outputs

Client 関数作成

実際に関数をApiClientクラス内に作成していきます。

()
abstract class ApiClient {
  factory ApiClient(Dio dio) = _ApiClient;

  ("/hoge/{id}")
  Future<GetHogeResponse> getHoge({
    () required String id,
  });

  ("/fuga")
  Future<PostFugaResponse> postFuga({
    ("month") required String month,
    () required PostFugaRequest body,
  });
}

関数の上に@GET("/hoge/{id}"), @POST("/fuga")と記載します。
これでメソッドとエンドポイントのpathを指定しています。


pathの動的な指定は引数で@Pathをつけることで、行います。
引数で指定するidは、pathの/{id}として実行されます。

  ("/hoge/{id}")
  Future<GetHogeResponse> getHoge({
    () required String id,
  });

呼び出し側でidを引数指定すると実際には以下のようなURLで実行されます。
先ほど指定したBaseOptionsのbaseUrlと組み合わせられています。

https://hoge-api-123456/hoge/1234hoge

Query,Bodyの指定も@Pathと同じく@Body,@Queryと指定して行います。

  ("/fuga")
  Future<PostFugaResponse> postFuga({
    ("month") required String month,
    () required PostFugaRequest body,
  });

Request,Response用のModel作成

freezedの自動生成で作成しています。

import 'package:freezed_annotation/freezed_annotation.dart';

part 'post_fuga.freezed.dart';
part 'post_fuga.g.dart';


class PostFugaRequest with _$PostFugaRequest {
  factory PostFugaRequest({
    required String name,
    required String code,
    required int amount,
  }) = _PostFugaRequest;

  factory PostFugaRequest.fromJson(
    Map<String, dynamic> json,
  ) =>
      _$PostFugaRequestFromJson(json);
}


class PostFugaResponse with _$PostFugaResponse {
  factory PostFugaResponse({required bool isValid}) = _PostFugaResponse;

  factory PostFugaResponse.fromJson(
    Map<String, dynamic> json,
  ) =>
      _$PostFugaResponseFromJson(json);
}

自動生成は先ほどと同じくbuild_runnerで行っています。

dart run build_runner build --delete-conflicting-outputs

freezedの詳しい説明はこの記事では省略します。
https://pub.dev/packages/freezed

作成したAPI処理を呼び出す

上記のようにしてAPIClientを作成したら、あとは呼び出して実行してあげるだけです!

Riverpodを利用して呼び出す

final hogeFutureProvider =
    FutureProvider((ref) => apiClient.getHoge(id: '1234hoge'));
Consumer(
  builder: (context, ref, _) {
    final hoge = ref.watch(hogeFutureProvider);
    return hoge.when(
      data: (data) {
        return Text(data.name);
      },
      loading: () {
        return const Text('load中');
      },
      error: (error, stackTrace) {
        return const Text('error');
      },
    );
  },
),

直接UIから呼び出す

ElevatedButton(
  onPressed: () {
    try {
      apiClient.postFuga(
        month: DateFormat("yyyyMM").format(DateTime.now()),
        body: PostFugaRequest(
          name: "sato",
          code: "1234sato",
          amount: 1000,
        ),
      );
    } on DioException {
      // error時の処理を記載
    }
  },
  child: const Text("Post Fuga"),
),

最後に

以上が dio + retrofit の簡単なコードになります!
私としては、dio + retrofitを導入してからAPI処理が冗長にならず管理しやすくなったと感じています。
皆さんの開発体験に寄与できれば嬉しいです。

Discussion