Search
Duplicate

[기초 모바일 스터디 7주차 과제]

소개

저번에 API 호출을 해봤을 때 어떠셨나요? 그냥 그럭저럭 호출할만 하셨나요?
아마 호출하는 부분은 괜찮았을 거라고 생각이 되지만, 호출해서 가져온 JSON 데이터들을 파싱하는 것이 상당히 까다로웠을 것이라고 생각이 됩니다. 파싱이라고 해봐야 사실 Dart에서 지원하는 Response를 사용하는 것이지만, response[’data’] 로 사용한다 하여도 JSON 구조의 깊이가 깊어지면 헷갈리기 쉽상입니다.
그래서 이번 주차에는 API 호출을 좀 더 쉽게 다룰 수 있는 방법에 대해서 알아볼 것입니다.

들어가기 앞서

도움이 되는 각종 패키지들에 대해 배워볼 것입니다.
패키지의 문서 읽기
플러터는 다른 프레임워크에 비해 그 역사가 짧기 때문에 한국어로 정리된 블로그 자료가 상대적으로 적습니다. 또한 패키지들이 빠르게 변화하기 때문에 간혹 블로그 자료가 과거의 패키지를 다룰 때도 있어 혼란을 야기하기도 합니다.
그래서 패키지 공식 문서를 보고 사용하는 것을 추천합니다!

과제

목표

JSON Serializable 패키지 사용에 대해 알아보자!
Retrofit 패키지 사용에 대해 알아보자!
Pretty dio logger 패키지 사용에 대해 알아보자!

과제 내용

6주차의 과제와 같습니다!
이번 과제는 아래의 영상과 같이 버튼을 눌렀을 때 날씨 정보를 받아오는 앱을 구현하는 것입니다!
허나 이번에는 꼭 Retrofit을 사용해서 구현해주세요!!!
이번엔 영상 제출은 안해주셔도 됩니다! 코드를 다 확인할 예정입니다~

자료 1 : JSON Serializable

API 호출을 하면 JSON의 형태로 데이터가 넘어오게 됩니다.
JSON 데이터를 클래스 인스턴스처럼 그냥 사용할 수 있으면 굉장히 편할 거 같지만, 그렇게 사용할 수 없기에 데이터를 한 번 가공해줘야 합니다.
이를 직렬화라고 부르고, factory라는 키워드를 통해 싱글톤으로 구성합니다.
이런 식으로 말이죠. ▼
factory Main.fromJson(Map<String, dynamic> json) => _$MainFromJson(json); Map<String, dynamic> toJson() => _$MainToJson(this);
Dart
복사
하지만 이는 함수 원형으로 작성된 것이고, 저희가 추가로 이 함수의 내용을 구현해야 직렬화가 됩니다.
그냥 이렇게만 작성한다고 직렬화가 되지 않습니다…
‘엄청 귀찮을듯…’
맞습니다. 진짜 엄청 귀찮기 때문에 저희는 JSON Serailizable 패키지를 사용할 것입니다.

1. JSON Serializable 패키지 설치

Json Serializable 패키지를 사용하기 위해서는 위의 두 패키지를 설치해야합니다.
설치는 굉장히 쉬우니 공식 문서를 참고해 가볍게 설치해줍시다!

2. Model 작성

JSON Serializable을 사용하기위해서는 아래와 같이 템플릿을 작성해야합니다.
이렇게 JSON이 온다고 하면, ▼
{ "coord": { "lon": -0.13, "lat": 51.51 },
JSON
복사
아래와 같이 작성을 해야합니다. ▼
import 'package:json_annotation/json_annotation.dart'; part 'coor.g.dart'; //파일이름이 gorani.dart 라면, gorani.g.dart 라고 작성! () // 어노테이션 class Coord { // 클래스 Coord({ required this.lon, required this.lat, }); // 생성자 final double lon, lat; // JSON 필드들 factory Coord.fromJson(Map<String, dynamic> json) => _$CoordFromJson(json); // 클래스 이름 + FromJson Map<String, dynamic> toJson() => _$CoordToJson(this); // 클래스 이름 + ToJson }
Dart
복사
이번 과제에서 오는 데이터는 아래와 같은데 이를 만들 때 어려움이 있을 거 같아, 제가 치트 시트를 드리겠습니다! ▼
{ "coord": { "lon": -0.13, "lat": 51.51 }, "weather": [ { "id": 300, "main": "Drizzle", "description": "light intensity drizzle", "icon": "09d" } ], "base": "stations", "main": { "temp": 280.32, "pressure": 1012, "humidity": 81, "temp_min": 279.15, "temp_max": 281.15 }, "visibility": 10000, "wind": { "speed": 4.1, "deg": 80 }, "clouds": { "all": 90 }, "dt": 1485789600, "sys": { "type": 1, "id": 5091, "message": 0.0103, "country": "GB", "sunrise": 1485762037, "sunset": 1485794875 }, "id": 2643743, "name": "London", "cod": 200 }
JSON
복사
Model 치트 시트

3. .g 파일 생성

앞에 모델만 작성했다고 끝이 나는 것이 아닙니다!
generator로 .g 파일까지 생성해줘야하는데, 이를 위해서는 아래의 패키지도 필요합니다.
build_runner 패키지도 설치가 완료되었다면, 아래와 같이 터미널에 명령어를 넣어부면 .g 파일이 생성됩니다! ▼
dart run build_runner build
Shell
복사
여기까지 했다면 이제 JSON 모델이 생성된 것입니다!

자료 2 : Retrofit

다음은 Retrofit 패키지 입니다.
Retrofit을 사용하려면 아래의 패키지들이 필요합니다.
JSON Serializable
JSON Annotation
Dio
Retrofit
Retrofit Generator
이 중 JSON 관련된 패키지는 앞에서 설치를 했기에 저희는 아래의 두 패키지만 설치해주면 됩니다.
아래와 같은 형태로 작성을 하여 API 관리를 훨씬 편하게 할 수 있습니다. ▼
( baseUrl: "기본 api 주소") abstract class AuthorityService { factory AuthorityService(Dio dio, {String baseUrl}) = _AuthorityService; // API1 ('/authority') Future<AddressGym> getMyGymList( ('authority-id') int authorityId, ('Authorization') String accessToken, ); // API2 ('/authority') Future<HttpResponse> registerMyGym( ('user-id') int userId, ('Authorization') String accessToken, () RegisterAuthorityGym gym); // API3 ('/authority/reservations') Future<ReservationListWithId> getMyReservationList( ('authority-id') int authorityId, ('Authorization') String accessToken, ); // API4 ('/authority/entry') Future<HttpResponse> enterGym( ('authority-id') int authorityId, ('Authorization') String accessToken, () EntranceTag entranceTag); }
Dart
복사

Dio - > Retrofit 변환

그러면 아래의 api 호출을 retrofit의 형태로 변환해보겠습니다.
String apiCall = "https://api.openweathermap.org/data/2.5/weather?q=$cityName&appid=$apikey"; final response = await Dio().get(apiCall);
Dart
복사
아래의 템플릿에 내용만 채우면 쉽게 수행할 수 있습니다.
part '파일이름.g.dart'; (baseUrl: "/*TODO : baseURL로 채웁시다.*/") abstract class /*Class NAME 을 적어주세요.*/ { factory /*Class NAME 을 적어주세요.*/(Dio dio, {String baseUrl}) = /*언더바 + Class NAME 을 적어주세요.*/; ('/*TODO : API 쿼리를 적어주세요.*/') Future</*반환 값 타입*/> /*API Method NAME 을 적어주세요*/( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

1. 파일 이름

가장 먼저 파일 이름을 위에 적어줍니다. 저의 경우엔 service로 했기에 service.g.dart가 됩니다.
(여기서 약간 눈치채셨겠지만, retrofit 역시도 generator로 생성된 g.dart 파일이 생깁니다.)
part 'service.g.dart'; (baseUrl: "/*TODO : baseURL로 채웁시다.*/") abstract class /*Class NAME 을 적어주세요.*/ { factory /*Class NAME 을 적어주세요.*/(Dio dio, {String baseUrl}) = /*언더바 + Class NAME 을 적어주세요.*/; ('/*TODO : API 쿼리를 적어주세요.*/') Future</*반환 값 타입*/> /*API Method NAME 을 적어주세요*/( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

2. baseUrl

위의 apiCall에서 “https://api.openweathermap.org/data/2.5/” 이 부분은 공통적인 baseUrl이 될 것입니다.
따라서 @RestApi 어노테이션에 들어가는 baseUrl은 “https://api.openweathermap.org/data/2.5/” 가 됩니다.
part 'service.g.dart'; (baseUrl: “https://api.openweathermap.org/data/2.5/) abstract class /*Class NAME 을 적어주세요.*/ { factory /*Class NAME 을 적어주세요.*/(Dio dio, {String baseUrl}) = /*언더바 + Class NAME 을 적어주세요.*/; ('/*TODO : API 쿼리를 적어주세요.*/') Future</*반환 값 타입*/> /*API Method NAME 을 적어주세요*/( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

3. class Name 과 factory

다음으로는 추상 클래스의 이름과 팩토리 클래스 이름입니다.
코드를 생성하면 클래스 이름 앞에 언더바가 붙은 형태로 생성이 됩니다.
다트의 팩토리 키워드를 통해 싱글톤 패턴으로 인스턴스를 할당하기에 이름을 통일시켜줘야 합니다.
저의 경우에는 WeatherService로 했기에 코드는 이렇게 됩니다.
part 'service.g.dart'; (baseUrl: “https://api.openweathermap.org/data/2.5/) abstract class WeatherService { factory WeatherService(Dio dio, {String baseUrl}) = _WeatherService(); ('/*TODO : API 쿼리를 적어주세요.*/') Future</*반환 값 타입*/> /*API Method NAME 을 적어주세요*/( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

4. 쿼리

이제부터 api들을 메소드의 형태로 작성해줍니다.
RestAPI의 규칙을 그대로 따라가기에 GET, PUT, POST, PATCH, DELETE 등의 어노테이션이 존재합니다.
저희가 저번에 사용했던 api는 GET에 해당하기에 @GET 어노테이션을 붙여주고, 뒤에는 저번에 사용했던 쿼리를 적어줍니다.
part 'service.g.dart'; (baseUrl: “https://api.openweathermap.org/data/2.5/) abstract class WeatherService { factory WeatherService(Dio dio, {String baseUrl}) = _WeatherService(); ('/weather?q={cityName}&appid={apiKey}') Future</*반환 값 타입*/> /*API Method NAME 을 적어주세요*/( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

5. 메소드 정의하기

가장 먼저 메소드 이름과 반환 값을 넣어봅시다.
저희는 이 메소드를 실행시키면 JSON 직렬화를 통해 Model 인스턴스로 데이터를 받을 것입니다.
Model 인스턴스요?
위의 JSON Serializable 패키지의 예시로 만들어놓은 클래스 인스턴스입니다.
제가 임의로 이름을 붙여놓은 것이고, 해당 API에서 주는 날씨 정보를 담은 클래스 인스턴스를 말합니다.
그렇기에 반환값의 타입은 Model이 될 것이며, 저의 경우엔 메소드 이름을 getWeather로 했기에 아래와 같이 메소드가 정의됩니다.
part 'service.g.dart'; (baseUrl: “https://api.openweathermap.org/data/2.5/) abstract class WeatherService { factory WeatherService(Dio dio, {String baseUrl}) = _WeatherService(); ('/weather?q={cityName}&appid={apiKey}') Future<Model> getWeather( /*필요한 매개변수들 넣기*/ ); }
Dart
복사

6. 필요한 매개변수들 넣기

쿼리를 보시면 저번에 작성했던 것과 조금 다른 것을 알 수 있을 것입니다.
String apiCall = "https://api.openweathermap.org/data/2.5/weather?q=$cityName&appid=$apikey"; final response = await Dio().get(apiCall);
Dart
복사
저번에는 중괄호쌍도 없고, $기호를 통해 String에 변수를 넣어주기만 했습니다.
하지만 retrofit에서는 그렇게 수행할 수 없습니다.
그 대신 다른 어노테이션들을 사용해 변수를 넣게 됩니다.
Header, Path, Query 등등… 다양한 어노테이션을 사용합니다. (자세한 내용은 공식 문서를 참고하시길 바랍니다! 내용이 너무나도 많아 여기서 전부 다룰 수 없습니다…)
그 중에서 저희는 쿼리 경로에 변수들을 넣을 것이기에 메소드를 구현하면서 매개변수에 해당 내용을 넣을 것입니다.
현재 저희가 쿼리에 넣을 변수는 cityNameapiKey입니다.
둘 다 String이고, 해당 쿼리의 Path로 지정된 자리에 들어가게 될 것입니다.
({cityName}, {apiKey} 로 자리를 지정해줬습니다.)
그렇기에 아래와 같이 매개변수가 어노테이션과 함께 지정됩니다.
part 'service.g.dart'; (baseUrl: “https://api.openweathermap.org/data/2.5/) abstract class WeatherService { factory WeatherService(Dio dio, {String baseUrl}) = _WeatherService(); ('/weather?q={cityName}&appid={apiKey}') Future<Model> getWeather( () String cityName, () String apiKey, ); }
Dart
복사

7. 마지막으로 build runner 돌리기!

마지막으로 아래의 코드를 터미널에 입력하여 build_runner를 실행시켜주면 됩니다.
dart run build_runner build
Shell
복사
이렇게 해서 파일이 하나 나오면, api 호출을 쉽게 해주는 클래스가 생성됩니다!
파일 생성이 안돼요…
파일 생성이 안되는 이유는 세 가지중 하나입니다.
1.
retrofit generator 패키지가 없음
2.
문법상 오류가 있음
3.
part ‘파일 이름.g.dart’ 를 선언하지 않음
이 세 가지 중 하나이니 잘 점검해보시고 계속 해도 안되면 질문 남겨주세요!

api 호출하기

잘 생성이 되었다면 아래의 형태로 호출할 수 있습니다!
Model model = await WeatherService(Dio()).getWeather(cityName, apikey);
Dart
복사

자료 3 : Pretty Dio Logger (짱 쉬움)

그런데 약간 걸리는 점이 있습니다.
내가 제대로 API를 호출했는지나 네트워크 에러 코드를 어떻게 확인하는지를 모릅니다.
똑같이 데이터를 불러오지 못했다고 해도, 에러 코드가 404일 수도 있고 500일 수도 있습니다.
둘은 천지차이입니다. 하지만 이 코드를 확인하지 못한다면 왜 에러가 나는지 백엔드 팀에게 알려줄 수 없습니다.
물론 response에 있는 내용을 직접 프린트 하는 것도 하나의 방법이지만… 조금 더 편하게 출력을 해봅시다.
가장 먼저 패키지를 설치해봅시다.
그리고 그 다음에 할 것은 굉장히 쉽습니다!
아래의 코드대로 따라해주시면 됩니다.
Dio dio = Dio(); dio.interceptors.add(PrettyDioLogger());
Dart
복사
커스텀이 필요하다면 아래와 같이 수정해주시면 됩니다!
dio.interceptors.add(PrettyDioLogger( requestHeader: true, requestBody: true, responseBody: true, responseHeader: false, error: true, compact: true, maxWidth: 90));
Dart
복사
이렇게 따라서 만들어준 dio 인스턴스를 아까 호출할 때 있던 Dio()자리에 넣어주기만 하면 됩니다.
Dio dio = Dio(); dio.interceptors.add(PrettyDioLogger()); Model model = await WeatherService(dio).getWeather(cityName, apikey);
Dart
복사
이렇게 호출하면 아래의 그림과 같이 상세하게 알려줍니다!
짱 예쁘게 정리해서 말이죠!

제출해야할 파일과 파일 경로

파일 경로 : 2024-1-Mobile-Study/Week7
제출 파일 : [필수] 구현 파일, [선택] 구동 영상

추가 명세

혹시 다른 API를 사용하고 싶으시다면 다른 API를 사용하셔도 좋습니다!
귀여운 포켓몬 API도 있답니다!
Retrofit을 사용하지 않았다면 과제는 수행하지 않은 것으로 체크됩니다!

마감 기한

2024년 5월 21일 23:59 까지

제출 방법

2024-1-Mobile-Study 라는 이름의 레포지토리의
Week7 폴더에 프로젝트 파일과 영상을 모두 올려주시면 됩니다!
2024-1-Mobile-Study/Week7