Flutter通信ライブラリ選定 ~ 選ばれたのはRetrofitでした ~

# Http通信を簡単に行うためのライブラリ選定を行いました
結論から言うと **dio + Retrofit** を採用しました
Flutter Dartの筆者は経験が薄い人間であることをご認識ください！！（コメントやLGTMいただけると嬉しいです！）
こちらの記事には dio + Retrofit での実装のみ記載します。（他の方法については個別記事を作成しましたのでそちらをご確認いただけると幸いです。）

## 選定対象のライブラリと簡単な特徴
#### １：　dioのみ　→ [Flutter : dioのみでの通信基盤作成](https://qiita.com/Morisan/items/0b90505000d5c3183b9b)
・httpの次に利用されているライブラリ
・リクエストクラスを個別に作成する必要あり

#### ２：　dio + Retrofit
・Retrofitがそもそもdioと一緒に使うことを想定している
・Android KotlinのRetrofit同様に個別にリクエストクラスを作成する必要はない
・レスポンスをオブジェクトで受け取ることができる　← これが他のライブラリと異なり、選ばれた理由　そして必要なクラスやファイルが少ない

#### ３：　http → [Flutter : httpでの通信基盤作成](https://qiita.com/Morisan/items/84a5204444dfcad24def)
・公式が開発したライブラリである
・正直あまりdioと使い勝手に差がないように感じた。（細かいところを見たら差があるかもしれませんが、、、）

#### ４：　chopper → [Flutter : chopperでの通信基盤作成](https://qiita.com/Morisan/items/e5c034eed184f24de27b)
・httpをラップしたライブラリである
・retrofitを意識して作られているため、実装感は似ているがgenerateされるのは少し違う感じがあった


# 実装
特徴を書くよりもコードを書いた方がわかりやすいと思うので、
RepositoryからAPIを呼び出しレスポンスオブジェクトを返すところまで記載します。
###### 前提
1. pubspec.yamlに`freezed`と`freezed_annotation`と`json_serializable`と`build_runner`を固定で入れておいてください。
2. 叩くAPIとしては、下記のようなログインAPIを例として挙げます
・Url →  `https://domain-hoge.com/fuga/piyo/login`
・Method → `POST`
・Header → `{ Content-Type: "applicatioin/json", "piyo": "hogehoge" }`
・BodyParameter → `{ id: String, pass: String}`
・ResponseJson → `{ base: { bar: String, toto: String } }`（レスポンスにbaseなどのキーが固定で入っている場合を考慮）

## dioのみ（dioをpubspec.yamlに追記する）
カスタムDioクラス
```dart:my_dio.dart
class MyDio with DioMixin implements Dio {
  @override
  set options(BaseOptions _options) {
    _options.baseUrl = "https://domain-hoge.com";
    _options.headers = {
      "Content-Type": "application/json",
      "piyo": "hogehoge"
    };
    super.options = _options;
  }

  @override
  HttpClientAdapter get httpClientAdapter => DefaultHttpClientAdapter();

  @override
  Interceptors get interceptors {
    final interceptor = super.interceptors;
    interceptor.add(
      InterceptorsWrapper(
        onResponse: (response, handler) {
          var res = response;
          if (response.statusCode == 200 && res.data['base'] != null) {
            res.data = res.data['base'];
          }
          return handler.next(res);
        }
      )
    );
    return interceptor;
  }
}
```
サービスごとに作成する（generatedなので下記ファイルを作成したら $`flutter pub run build_runner build`をする）
```dart:auth_service.dart
part 'auth_service.g.dart'

@RestApi()
abstract class AuthService {
  factory Client(MyDio dio) = _AuthService;

  @POST("/fuga/piyo/login")
  Future<LoginResponse> login(@Body() LoginRequestParameter parameter);
}
```
RepositoryからAuthServiceのloginを呼び出す
```dart:login_repository.dart
abstract class LoginDataSource {
  Future<Result<LoginResponse>> login(LoginModel loginModel);
}

class LoginRepository extends LoginDataSource {
  @override
  Future<Result<LoginResponse>> login(LoginModel loginModel) async {
    // LoginRequestParameterとResponseオブジェクトはfreezedとjson_serializableを利用してfromJsonのfactoryを作成しておくこと
    final parameter = LoginRequestParameter(id: loginModel.id, pass: loginModel.pass);

    try {
      return Result.success(await AuthService(MyDio()).login(parameter));
    } catch (error) {
      return Result.failure();
    }
}
```
リザルトクラス（上記同様にflutter pub run build_runner build ~ する）
```dart:result.dart
part 'result.freezed.dart';

@freezed
class Result<T> with _$Result<T> {
  const factory Result.success(T value) = Success<T>;
  const factory Result.failure() = Failure<T>;
}
```
ログインリクエストのパラメータ（freezedなので下記ファイルを作成したら、$`flutter pub run build_runner build --delete-conflicting-outputs`を実行する）
```dart:login_request_parameter.dart
part 'login_request_parameter.freezed.dart';
part 'login_request_parameter.g.dart';

@freezed
class LoginRequestParameter with _$LoginRequestParameter {
  factory LoginRequestParameter({
    required String id,
    required String pass,
  }) = _LoginRequestParameter;

  factory LoginRequestParameter.fromJson(Map<String, dynamic> json) => _$LoginRequestParameterFromJson(json);
}
```
レスポンス受け取るクラス（上記同様にflutter pub run build_runner build ~ する）
```dart:login_response.dart
part 'login_response.freezed.dart';
part 'login_response.g.dart';

@freezed
class LoginResponse with _$LoginResponse {
  factory LoginResponse({
    required String bar,
    required String toto,
  }) = _LoginResponse;

  factory LoginResponse.fromJson(Map<String, dynamic> json) => _$LoginResponseFromJson(json);
}
```