こちらも合わせてご覧ください
Flutter通信ライブラリ選定 ~ 選ばれたのはRetrofitでした ~
簡単な特徴
- 新しいAPIごとに作成する必要があるのは、
・Repositoryのクラス(メソッド)
・RequestParameterクラス
・Response受け取るクラス - Converterで無理やりconvertしてあげなきゃいけない
- httpのラッパーライブラリ
実装
特徴を書くよりもコードを書いた方がわかりやすいと思うので、
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などのキーが固定で入っている場合を考慮) - chopperをpubspec.yamlに追記する
具体的実装
クライアント生成 必要なInterceptorとConvertor
client.dart
class Client extends ChopperClient {
KDClient()
: super(converter: CustomConvertor(), interceptors: [CustomHeadersInterceptor.create()]);
@override
String get baseUrl => "https://domain-hoge.com";
}
class CustomConvertor implements Converter {
@override
Request convertRequest(Request request) {
final req = applyHeader(
request,
contentTypeKey,
jsonHeaders,
override: false,
);
return encodeJson(req);
}
@override
FutureOr<Response<BodyType>> convertResponse<BodyType, InnerType>(
Response response) {
return decodeJson<BodyType, InnerType>(response);
}
Request encodeJson(Request req) {
var contentType = req.headers[contentTypeKey];
if (contentType != null && contentType.contains(jsonHeaders)) {
return req.copyWith(body: json.encode(req.body));
}
return req;
}
Response<BodyType> decodeJson<BodyType, InnerType>(Response response) {
var contentType = response.headers[contentTypeKey];
var body = response.body;
if (contentType != null && contentType.contains(jsonHeaders)) {
body = utf8.decode(response.bodyBytes);
}
try {
var mapData = json.decode(body);
var responseBase = mapData["base"];
BodyType bodyType;
switch (BodyType) {
case LoginResponse:
bodyType = LoginResponse.fromJson(data as Map<String, dynamic>) as BodyType;
break;
case String:
bodyType = responseBase.data as BodyType;
break;
default:
bodyType = responseBase as BodyType;
}
return response.copyWith<BodyType>(body: bodyType);
} catch (e) {
chopperLogger.warning(e);
return response.copyWith<BodyType>(body: body);
}
}
}
RepositoryからClient.callを呼び出す
login_repository.dart
abstract class LoginDataSource {
Future<Result<LoginResponse>> login(LoginModel loginModel);
}
class LoginRepository extends LoginDataSource {
LoginRepository() : service = AuthService.create(Client());
AuthService service;
@override
Future<Result<LoginResponse>> login(LoginModel loginModel) async {
final parameter = LoginRequestParameter(
id: loginModel.id, pass: loginModel.pass);
try {
final response = await service.login(parameter);
return Result.success(response.body!);
} catch (e) {
return Result.failure();
}
}
}
リクエストを叩くChopperServiceクラスの作成
下記クラスを作成したらgenerateしましょう
auth_service.dart
part 'auth_service.chopper.dart';
@ChopperApi(baseUrl: "/fuga/piyo")
abstract class AuthService extends ChopperService {
static AuthService create([ChopperClient? client]) => _$AuthService(client);
@Post(path: "/login")
Future<Response<LoginResponse>> login(@Body() LoginRequestParameter parameter);
}
リクエスト等の基幹クラス
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 ~ する)
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>;
}
ログインリクエスト
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
を実行する)
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 ~ する)
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);
}