Http通信を簡単に行うためのライブラリ選定を行いました
結論から言うと dio + Retrofit を採用しました
Flutter Dartの筆者は経験が薄い人間であることをご認識ください!!(コメントやLGTMいただけると嬉しいです!)
こちらの記事には dio + Retrofit での実装のみ記載します。(他の方法については個別記事を作成しましたのでそちらをご確認いただけると幸いです。)
選定対象のライブラリと簡単な特徴
1: dioのみ → Flutter : dioのみでの通信基盤作成
・httpの次に利用されているライブラリ
・リクエストクラスを個別に作成する必要あり
2: dio + Retrofit
・Retrofitがそもそもdioと一緒に使うことを想定している
・Android KotlinのRetrofit同様に個別にリクエストクラスを作成する必要はない
・レスポンスをオブジェクトで受け取ることができる ← これが他のライブラリと異なり、選ばれた理由 そして必要なクラスやファイルが少ない
3: http → Flutter : httpでの通信基盤作成
・公式が開発したライブラリである
・正直あまりdioと使い勝手に差がないように感じた。(細かいところを見たら差があるかもしれませんが、、、)
4: chopper → Flutter : chopperでの通信基盤作成
・httpをラップしたライブラリである
・retrofitを意識して作られているため、実装感は似ているがgenerateされるのは少し違う感じがあった
実装
特徴を書くよりもコードを書いた方がわかりやすいと思うので、
RepositoryからAPIを呼び出しレスポンスオブジェクトを返すところまで記載します。
前提
- pubspec.yamlに
freezed
とfreezed_annotation
とjson_serializable
とbuild_runner
を固定で入れておいてください。 - 叩く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クラス
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
をする)
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を呼び出す
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 ~ する)
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
を実行する)
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 ~ する)
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);
}