Flutter : httpでの通信基盤作成

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

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

# 実装
特徴を書くよりもコードを書いた方がわかりやすいと思うので、
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などのキーが固定で入っている場合を考慮）
httpをpubspec.yamlに追記する

### 具体的実装
リクエストを送るClient
```dart:client.dart
class Client extends BaseClient {
  Future<Response> call(RequestBase request) {
    var json = jsonEncode(request.parameter);
    switch (request.apiMethod) {
      case HttpMethod.post:
        return post(request.url, headers: request.headers, body: json);
      case HttpMethod.get:
        return get(request.url, headers: request.headers);
      case HttpMethod.put:
        return put(request.url, headers: request.headers, body: json);
      case HttpMethod.patch:
        return patch(request.url, headers: request.headers, body: json);
      case HttpMethod.delete:
        return delete(request.url, headers: request.headers, body: json);
    }
  }

  @override
  Future<StreamedResponse> send(BaseRequest request) {
    // TODO: implement send
    throw UnimplementedError();
  }
}
```
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);

    try {
      final response = await Client().call(request);
      final data = json.decode(response.body) as Map<String, dynamic>;
      return Result.success(LoginResponse.fromJson(data["base"]));
    } catch (error) {
      return Result.failure();
    }
  }
}
```
リクエスト等の基幹クラス
```dart:http_request_base.dart
// リクエストの基幹
abstract class HttpRequestBase {
  HttpMethod get apiMethod;
  String get path;
  RequestParameter? get parameter;

  String get method => apiMethod.name.toUpperCase();

  Uri get url {
    if (apiMethod == HttpMethod.get && parameter != null) {
      return Uri.https("domain-hoge.com", path, parameter!.toJson());
    }
    return Uri.https("domain-hoge.com", path);
  }

  Map<String, String> get headers => { Content-Type: "applicatioin/json", "piyo": "hogehoge" };
}

enum HttpMethod {
  post,
  get,
  put,
  patch,
  delete
}
```
リザルトクラス（上記同様に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 extends HttpRequestBase {
  LoginRequest(this.loginParameter) : super();
  
  final LoginRequestParameter loginParameter;
  
  @override
  HttpMethod get apiMethod => HttpMethod.post;

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

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