50
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Flutter】HTTPクライアントパッケージ「Chopper」でAPI通信をする

Last updated at Posted at 2020-01-27

Chopperとは

Chopperとは、Dart製のHTTPクライアントのラッパー的な便利パッケージです。内部的には、公式のhttpパッケージを使ってAPI通信をしています。
また、AndroidのRetrofitのようにインターフェースを定義しただけでクライアントを呼び出す部分の実装を自動生成してくれるので、とても便利です。今回は、この「Chopper」を使ってAPI通信をする方法について簡単に解説していきます。
プラグインページは以下です。

環境

以下の環境を想定しています。
スクリーンショット 2020-01-19 16.43.48.png

1. プラグインのインストール

まずは、インストールです。pubspec.yamlを編集します。執筆時点で最新のバージョンを指定しています。

pubspec.yml
name: 
description: 

...

dependencies:
  flutter:
    sdk: flutter
  ...
  chopper: ^3.0.1+1 // Add

dev_dependencies:
  flutter_test:
    sdk: flutter
  ...
  build_runner: ^1.7.3  // Add
  chopper_generator: ^3.0.1+1  // Add

2. インターフェースを定義する

ますここで必要なのは、

  • partで自動生成用コードのパスを指定する。
  • @ChopperApi()アノテーションを付与
  • ChopperServiceを継承させたabstractクラスにする
  • create()メソッドを定義

その後、エンドポイントをメソッドとして定義します。

service.dart
import 'package:chopper/chopper.dart';

part 'vegetable_service.chopper.dart';

@ChopperApi(baseUrl: '/v1')
abstract class VegetableService extends ChopperService {
  static VegetableService create([ChopperClient client]) =>
      _$VegetableService(client);
  
   ...
}

使用できるHTTPメソッド

HTTPメソッドは、以下がアノテーションで使用可能です。

@Get
@Post
@Delete
@Put
@Patch
@Head

例えば以下のようになります。

service.dart
import 'package:chopper/chopper.dart';

part 'vegetable_service.chopper.dart';

@ChopperApi(baseUrl: '/v1')
abstract class VegetableService extends ChopperService {
  static VegetableService create([ChopperClient client]) =>
      _$VegetableService(client);

  @Get(path: "/vegetables")
  Future<Response> fetchVegetables({
    @Query('page[number]') int pageNumber = 1,
  });

  @Get(path: "/vegetables/{vegetable_id}")
  Future<Response> fetchVegetables({
    @Path('vegetable_id') int vegetableId,
  });

  @Post(path: '/vegetables')
  Future<Response> postVegetable(@Body() VegetableForm vegetableForm);
}

urlのpathにパラメータを指定する

@Get(path: "/vegetables/{vegetable_id}")のように波括弧でまずパラメータ名を決めます。

@Path()アノテーションを使って、{}内のnameと@Path()のパラメータを一致させます。

service.dart
@Get(path: "/vegetables/{vegetable_id}") 
Future<Response> fetchVegetables({
  @Path('vegetable_id') int vegetableId,  // 上の{vegetable_id}と@Path('vegetable_id')を一致させる。
});

クエリストリングにパラメータを指定する

@Query()アノテーションを使って、メソッドの引数に、指定したいパラメータを指定します。

service.dart
@Get(path: "/vegetables")
Future<Response> fetchVegetables({
  @Query('page[number]') int pageNumber = 1,
});

リクエストボディを設定する

@Body()アノテーションを使って、メソッドの引数にリクエストボディとして送信したいモデルを指定します。

service.dart
@Post(path: '/vegetables')
Future<Response> postVegetable(
  @Body() VegetableForm vegetableForm,
);

3. 自動生成コマンドを実行

以下のコマンドをFlutterプロジェクト直下で実行すると、2で定義したインターフェースに基づいて、実装ファイルが生成されます。
出力されるファイル名はpartで指定した名前になります。

$ flutter packages pub run build_runner build --delete-conflicting-outputs

4. ChopperClientを生成

採用しているアプリのアーキテクチャにもよりますが、RepositoryなどのAPIコールをしたいエンドポイントで、ChopperClientを初期化します。私が今作っているアプリでは、ChopperClientCreatorというChopperClientを生成するクラスを作り、Repositoryで生成するようにしています。

chopper_client_creator.dart
class ChopperClientCreator {
  static final String baseUrl = "https://hogefuga.xyz";

  static ChopperClient create() {
    return ChopperClient(
      baseUrl: ChopperClientCreator.baseUrl,
      converter: JsonConverter(),
    );
  }
}

Repositoryにて、ChopperClientCreatorを経由してChopperClientを初期化します。

repository.dart
class Repository {
  final VegetableService service = 
 VegetableService.create(ChopperClientCreator.create());
  
  ...
}

ここでは、あまりテスタビリティは考えていません。。

5. APIコール&jsonのパース

Repositoryにて、Serviceを利用して、APIをコールします。

repository.dart
class Repository {
  final VegetableService service = 
 VegetableService.create(ChopperClientCreator.create());
  
  Future<List<Vegetable>> fetchVegetables() async {
    final Response response = await service.fetchVegetables();
    if (response.isSuccessful) {
      final responseBodyJson = response.body as Map<String, dynamic>;
      // jsonのパース
    }
  }
}

補足

Converterの設定

上記では触れていないのですが、ChopperClient初期化時にConverterを指定することができます。
これを指定すると、こちらで定義したデータ型で、レスポンスを返してくれるようになります。

Chopperでは以下のconverterがデフォルトで用意されています。

  • JsonConverter
  • FormUrlEncodedConverter

ConverterはChopperClientの生成時に指定します。

client_creator.dart
ChopperClient(
      baseUrl: ChopperClientCreator.baseUrl,
      converter: JsonConverter(), // Add
    );

JsonConverter

こちらを指定すると以下のようなResponseクラスやRequestクラス(コードは割愛)にパースしてくれます。

Response.dart
class Response<BodyType> {
  final http.BaseResponse base;
  final BodyType body;

  Response(this.base, this.body);

  Response replace<NewBodyType>({http.BaseResponse base, NewBodyType body}) =>
      Response<NewBodyType>(base ?? this.base, body ?? this.body);

  int get statusCode => base.statusCode;

  bool get isSuccessful => statusCode >= 200 && statusCode < 300;

  Map<String, String> get headers => base.headers;

  Uint8List get bodyBytes =>
      base is http.Response ? (base as http.Response).bodyBytes : null;

  String get bodyString =>
      base is http.Response ? (base as http.Response).body : null;
}

カスタムのConverter

カスタムでConverterを設定したければ、以下のConverterを継承したクラスを実装すれば設定できると思います。

Converter.dart
@immutable
abstract class Converter {
  FutureOr<Request> convertRequest(Request request);

  /// [BodyType] is the expected type of your response
  /// ex: `String` or `CustomObject`
  ///
  /// In the case of [BodyType] is a `List` or `BuildList`
  /// [InnerType] will be the type of the generic
  /// ex: `convertResponse<List<CustomObject>, CustomObject>(response)`
  FutureOr<Response<BodyType>> convertResponse<BodyType, InnerType>(
      Response response);
}

ErrorConverterの設定

こちらはリクエストがErrorだった際に使用されるコンバータです。JsonConverterと同様に以下が利用できます。

  • JsonConverter
  • FormUrlEncodedConverter

上記のようなErrorConverterはChopperClientの生成時に指定します。

client_creator.dart
ChopperClient(
      baseUrl: ChopperClientCreator.baseUrl,
      converter: JsonConverter(),
      errorConverter: JsonConverter(),  // Add
    );

カスタムのErrorConverter

カスタムでErrorConverterを設定したければ、以下のErrorConverterを継承したクラスを実装すれば設定できると思います。

ErrorConverter.dart
abstract class ErrorConverter {
  FutureOr<Response> convertError<BodyType, InnerType>(Response response);
}

interceptorの設定

interceptorを利用すると、主に以下の二つができます。

  • HTTPHeaderの指定
  • Logging

HTTPHeaderの指定

HTTPヘッダーを設定するには、ChopperClient生成時に、interceptorsのプロパティを使って設定します。

client_creator.dart
ChopperClient(
          baseUrl: BlaboClientCreator.baseUrl,
          converter: JsonConverter(),
          errorConverter: JsonConverter(),
          interceptors: [ // Add
            (Request request) async => request.replace(
                  headers: {
                    'Content-Type': 'application/json',
                    "Authorization": 'abcdefghijkhogehoge',
                    "Accept-Encoding": "gzip",
                  },
                ),
          ],
        );

また、chopperには以下のような定数が用意されているので、使えるものは使うと良さそうです。

constants.dart
const contentTypeKey = 'content-type';
const jsonHeaders = "application/json";
const formEncodedHeaders = "application/x-www-form-urlencoded";

定数を使用するとこのような使い方になります。

chopper_client.dart
ChopperClient(
          baseUrl: BlaboClientCreator.baseUrl,
          converter: JsonConverter(),
          errorConverter: JsonConverter(),
          interceptors: [ // Add
            (Request request) async => request.replace(
                  headers: {
                    contentTypeKey: contentType, // 定数を使用
                    "Authorization": 'abcdefghijkhogehoge',
                    "Accept-Encoding": "gzip",
                  },
                ),
          ],
        );

Logging

interceptorsを使うと、HTTPリクエストのロギングもできます。以下のように記述するとログに通信内容を出力することができます。

client_creator.dart
ChopperClient(
          ...
          interceptors: [
            (Request request) async => request.replace(
                  headers: {
                    contentTypeKey: contentType,
                    "User-Agent": "", // TODO: UserAgentの取得,
                    "Authorization": await getAccessToken(),
                    "Accept-Encoding": "gzip",
                  },
                ),
            (Request request) async {  // Add (Requestのロギング)
              debugPrint("""
                =========HTTP Request logging=========
                baseUrl: ${request.baseUrl}
                url: ${request.url}
                parameter: ${request.parameters}
                method: ${request.method}
                headers: ${request.headers}
                body: ${request.body}
                multipart: ${request.multipart}
                parts: ${request.parts}
                ======================================
              """);
              return request;
            },
            (Response response) async {  // Add (Responseのロギング)
              debugPrint("""
                =========HTTP Response logging=========
                url: ${response.base.request.url}
                status: ${response.statusCode}
                headers: ${response.headers}
                body: ${response.body}
                ======================================
              """);
              return response;
            },
          ],
        );

まとめ

Chopperの日本語の記事が少なかったので、今回取り上げてみました。AndroidのRetrofitやOkHttpと利用方法が似ているので、個人的には便利なのではと思います。

50
44
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
44

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?