Flutter : dioのみでの通信基盤作成

# こちらも合わせてご覧ください 
[Flutter通信ライブラリ選定 ~ 選ばれたのはRetrofitでした ~](https://qiita.com/Morisan/items/9688ab92ff94024353c5)

## 簡単な特徴
1. 新しいAPIごとに作成する必要があるのは、
・Repositoryのクラス（メソッド）
・Requestクラス
・RequestParameterクラス
・Response受け取るクラス
2. Client.callから帰ってくるクラスが<Map<String, dynamic>>のため都度呼び出し元でfromJsonをしてオブジェクトに変換しなければならない

# 実装
特徴を書くよりもコードを書いた方がわかりやすいと思うので、
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();
}
```
作成したDioを使ってリクエストを送るClient
```dart:client.dart
class Client {
  // APIRequestクラスとResultクラス、APIMethodは後述する
  static Future<Result<Map<String, dynamic>>> call(APIRequest request) async {
    final dio = MyDio();
    dio.options = BaseOptions(
      method: request.method.name.toUpperCase(),
    );

    Response response;
    try {
      if (request.method == APIMethod.get) {
        response = await dio.request(
            request.path,
            queryParameters: request.parameter?.toJson(),
        );
      } else {
        response = await dio.request(
            request.path,
            data: request.parameter?.toJson(),
        );
      }
    } catch (e) {
      return Result.failure();
    }

    return Result.success(response.data["base"]);
  }
}
```
RepositoryからClient.callを呼び出す
```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 {
    final parameter = LoginRequestParameter(id: loginModel.id, pass: loginModel.pass);
    final request = LoginRequest(parameter);

    final result = await Client.call(request);
    Result<LoginResponse> response = Result.failure();
    result.when(
        success: (Map<String, dynamic> json) {
          response = Result.success(LoginResponse.fromJson(json));
        },
        failure: () {
          response = Result.failure();
        }
    );

    return response;
  }
}
```
リクエスト等の基幹クラス
```dart:http_request_base.dart
// リクエストの基幹
abstract class HttpRequestBase {
  HttpMethod get method;
  String get path;
  RequestParameter? parameter;
}
enum HttpMethod {
  post,
  get,
  put,
  patch,
  delete
}
// リクエストパラメータのBase
abstract class RequestParameter {
  Map<String, dynamic> toJson();
}
```
リザルトクラス（上記同様に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>;
}
```
ログインリクエスト
```dart:login_request.dart
class LoginRequest implements HttpRequestBase {
  LoginRequest(this.parameter);

  @override
  APIMethod method = APIMethod.post;

  @override
  String path = "/fuga/piyo/login";

  @override
  RequestParameter? parameter;
}
```
ログインリクエストのパラメータ（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);
}
```