[Flutter] retrofit 사용하기
retrofit | Dart package
retrofit.dart is an dio client generator using source_gen and inspired by Chopper and Retrofit.
pub.dev
pubspec.yaml
dependencies:
retrofit: '>=4.0.0 <5.0.0'
logger: any #for logging purpose
json_annotation: ^4.8.1
dev_dependencies:
retrofit_generator: '>=7.0.0 <8.0.0' // required dart >=2.19
build_runner: '>=2.3.0 <4.0.0'
json_serializable: ^6.6.2
json_serializable과 함께 사용하면 좋다는 것을 알 수 있다,
기존의 Json 통신구조를 살펴보자.
- Future함수를 통해 http통신을 진행해 변환되지않은 JSON코드 받아오기
- JSON코드를 모델에 담아 직렬화
- 직렬화된 JSON을 FutureBuilder에 담아 실행.
FuterBuilder를 사용하는 모든 부분에서 이 과정들이 반복된다.
모델만 자동화하지말고 API요청부터 모델로 만드는 과정까지 전부 자동화하자! 가 retrofit의 의의다.
repository.dart
import 'package:dio/dio.dart' hide Headers;
import 'package:retrofit/retrofit.dart';
part 'restaurant_repository.g.dart';
@RestApi()
abstract class RestaurantRepository {
// <http://$ip/restaurant>
factory RestaurantRepository(Dio dio, {String baseUrl}) =
_RestaurantRepository;
// <http://$ip/restaurant/>
@GET('/')
@Headers({
'accessToken': 'true',
})
Future<CursorPagination<RestaurantModel>> paginate();
// <http://$ip/restaurant/:id>
@GET('/{id}')
@Headers({
'accessToken': 'true',
})
Future<RestaurantDetailModel> getRestaurantDetail({
@Path() required String id,
});
}
facotry RestaurantRepository로 선언하는 이유
- factory로 선언하면 좋아서 =? 아님, retrofit 사용 형식에 맞추기 위해서임
dio는 외부에서 생성되는 dio를 받아서 사용해야한다.
Why? 여러 개의 레포지토리에서 같은 dio 인스턴스를 공유해야하는 이유가 있다.
_RestaruantRepository; 는 repository.g.dart 파일에 자동으로 생성될것이다.
여기까지는 그대로 작성하고 그 아래에는 통신들을 구현한다.
레트로핏에서는 실제로 응답을 받는 형태와 완전히 똑같은 형태의 클래스를 반환값으로 넣어줘야 합니다.
그럼 자동으로 json값이 매핑이 되어서 클래스의 인스턴스가 됩니다.
그런데 Future를 붙이는 이유는 api 요청이기때문애 외부에서 와서
path parameter를 넣어야하는 경우 () 안에 선언해준다
@Get('/{id}')
Future<RestaurantDetailModel> getRestaurantDetail({
@Path() required String id
})
이때 이름이 같으면 자동으로 매핑해준다.
이름이 다르다면
@Get('/{id}')
Future<RestaurantDetailModel> getRestaurantDetail({
@Path('id') required String sid
})
만들어진 코드를 확인해보자
repository.g.dart
@override
Future<RestaurantDetailModel> getRestaurantDetail({required id}) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{r'accessToken': 'true'};
_headers.removeWhere((k, v) => v == null);
final _data = <String, dynamic>{};
final _result = await _dio.fetch<Map<String, dynamic>>(
_setStreamType<RestaurantDetailModel>(
Options(method: 'GET', headers: _headers, extra: _extra)
.compose(_dio.options, '/${id}',
queryParameters: queryParameters, data: _data)
.copyWith(baseUrl: baseUrl ?? _dio.options.baseUrl)));
final value = RestaurantDetailModel.fromJson(_result.data!);
return value;
}
이전에 포스팅한 Model.fromJson 이 자동으로 만들어져있는 것을 확인할 수 있다.
이를 통해 통신과 모델 직렬화과정을 한번에 할 수 있다.
사용하는 방법
Future<RestaurantDetailModel> getRestaurantDetail() async {
final dio = Dio();
// 토큰 관리
dio.interceptors.add(
CustomInterceptor(
storage: storage,
),
);
// retrofit에 생성한 dio와 baseurl을 넣어주고 path파라미터를 담은 함수를 반환한다.
final repository = RestaurantRepository(dio, baseUrl: '<http://$ip/restaurant>');
return repository.getRestaurantDetail(id: id);
}