[Flutter] retrofit 사용

Hee Tae Shin·2023년 1월 12일
2
post-thumbnail

상황

프로젝트 하면, Repository 를 만드는 경우가 생긴다.
그 이유와 사용법을 알아보자!

패키지 설치

https://pub.dev/packages/retrofit

공통적인 부분

api 요청부터 모델을 만드는 과정을 전부다 자동화로 만들자! 그래서 이 패키지가 나온것이다.

api 요청 부분 두군데를 비교해보곘다
반환 받는 값이랑 요청 URL 전환되는 모데일이 다르다.
과정은 같다.
1. api 요청을 한다.
2. api 응답을 받는다.
3. 모델로 변경한다. (해당모델의 constructor 인 fromJson()을 사용해서!)
4. 다른값들을 추가로 전달해준다.

repository 폴더 생성

사실은 repository 라고 하기엔 많이 부족하지만, 이렇게 명명하자

retrofit 의 stardard 를 맞춰서 작성을 해주도록 하자

retrofit 은 api 에서 실제로 받는 응답 형태와 클래스의 반환값에 완전히 똑같은 형태를 넣어줘야한다는 것이 굉장히 중요하다!
그러면 자동으로 JSON 형태의 값이 매핑되어 클래스의 인스턴스가 된다.

import 'package:authentication_study/restaurant/model/restaurant_detail_model.dart';
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';

part 'restaurant_repository.g.dart';

()
abstract class RestaurantRepository {
  // baseURL = http://$ip/restaurant;
  factory RestaurantRepository(Dio dio, {String baseUrl}) =
      _RestaurantRepository;

  // http://$ip/restaurant/;
  // @GET("/")
  // paginate();

  // http://$ip/restaurant/:id;
  ("/{id}")
  Future<RestaurantDetailModel> getRestaurantDetail({
    // id 변수를 매변 요청할 때마다 넣어줘야하는데, @Path 어노테이션으로 정의해주면 된다.
    () required String id,
  });
}

g.dart 자세히보기

1) 자동으로 getRestaurantDetail 함수가 생긴걸 볼 수 있다.
2) dio 로 fetch 를 하는데, Options 를 보면 GET, headers 담아서, extra 까지 보내고, '/id' 값을 넣는다.
3) 응답이 오면 RestaurantDetailModel.fromJson 에 자동으로 매핑을 하고
value 에 담아서 리턴해준다.

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'restaurant_repository.dart';

// **************************************************************************
// RetrofitGenerator
// **************************************************************************

// ignore_for_file: unnecessary_brace_in_string_interps,no_leading_underscores_for_local_identifiers

class _RestaurantRepository implements RestaurantRepository {
  _RestaurantRepository(
    this._dio, {
    this.baseUrl,
  });

  final Dio _dio;

  String? baseUrl;

// 1) 설명 참조
  
  Future<RestaurantDetailModel> getRestaurantDetail({required id}) async {
    const _extra = <String, dynamic>{};
    final queryParameters = <String, dynamic>{};
    final _headers = <String, dynamic>{};
    final _data = <String, dynamic>{};
    // 2) 설명 참조
    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)));
            // 3) 설명 참조
    final value = RestaurantDetailModel.fromJson(_result.data!);
    return value;
  }

  RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
    if (T != dynamic &&
        !(requestOptions.responseType == ResponseType.bytes ||
            requestOptions.responseType == ResponseType.stream)) {
      if (T == String) {
        requestOptions.responseType = ResponseType.plain;
      } else {
        requestOptions.responseType = ResponseType.json;
      }
    }
    return requestOptions;
  }
}

이제 retrofit 대체할 시간!

  1. api 통신하는 부분을 다 지우고 retrofit 에 맞게 적용을 해주자!
    저렇게 긴 코드가 2줄로 줄어들고, g.dart 파일에 자동으로 로직이 추가된다.

  2. repository.dart 파일에 retrofit 으로 선언했으니 맞게 변경을 해준다.

설명 1 )
api 요청 반환타입에 맞춰서 타입도 변경을 해준다.

삭제 ! )
이 뷰단에서 매핑하는 함수를 호출할 필요가 없다. 왜냐, g.dart 에서 retrofit 이 다~ 생성이 했기 떄문이다.

에러 해결!

액세스 토큰 오류로 401 에러가 나니깐 우선은 강제로 헤더를 넣어주자

// headers 를 두군데에서 가져온다고 한다면 하나를 숨겨주자
import 'package:dio/dio.dart' hide Headers;

("/{id}")
// header 강제로 넣기
({
    'authorization': 'Bearer ~~~ 액세스 토큰 넣어준다.'
  })
  ...

나아진 점!

원래는 api 요청을 하고, 응답 받아서 매핑하는 과정을 2줄로 줄여서 작업할 수있다. 나머지 작업들은 g.dart 파일에 로직이 다 구현이 되어있다.

profile
안녕하세요

1개의 댓글

comment-user-thumbnail
2023년 1월 12일

많이 도움 됐습니다~

답글 달기