목표
Retrofit은 REST API 매핑을 자동으로 진행하여주며 JSONSerializable과 상성이 좋은 패키지이다. Retrofit을 이용하여 API 매핑을 진행하여보자.
*해당 내용은 본인의 개발 공부에 있어서 여러가지 참고하며 기록했던 내용을 복기하고 이해하고자 작성한 블로그 글입니다. 같이 공부를 해나가며 Flutter라는 언어에 취미를 가지신 분 들이 개념을 잡는데 도움이 되었으면 합니다. 참고한 홈페이지 등에 대해서는 각 페이지별 하단에 명시하여 두었습니다. 수정 및 문의 사항이 있으면 알려주시길 바랍니다.
정의
Retrofit은 Android에서 REST API를 쉽게 사용할 수 있는 클라이언트 라이브러리이다. 서버와 클라이언트 간 http 통신을 위한 라이브러리라고 쉽게 설명할 수 있다. 예를 들어, 사이트의 공지사항이나 사이트의 특정 상품을 모아서 보여준다던가 등을 구현할 수 있다.
추가 설명
조금 더 쉬운 표현을 쓰자면 Retrofit은 REST API를 호출하는 네트워킹 라이브러리로써 JSON 데이터를 모델로 파싱하거나 GET, POST, PUT 등의 요청을 쉽게 만들 수 있다. 예를 들어, 인터넷에서 날씨 정보나 뉴스를 보고 싶을때, Retrofit은 원하는 것을 다른 컴퓨터에게 물어보고 답변을 받아 보여주는 역할을 한다.
장점통신을 할때 JSON 형태에 맞는 모델 클래스를 만들어서 사용하므로 JSON을 다루기 편리하다. 예를 들어 서버에서 응답받은 JSON을 별도의 파싱없이 사용할 수 있다.
기본개념
사용법을 알아보기 전에 기본 구조를 알아보자.
정보를 요첨함에 있어서 다음의 4가지 구성이 필요하다.1. 요청하는 주소2. 응답의 형태3. 요청의 형태4. 요청시 사용하는 파라미터
1. 요청하는 주소예시: 배민 CEO 사이트
URL 요청하기
접속: https://ceo.baemin.com/notice
공지사항 | 배민외식업광장
배민사장님서비스 이용과 관련한 각종 공지사항을 안내합니다. 서비스 안내, 점검 안내, 약관 안내를 통해 편리하게 배민서비스를 이용하세요.
ceo.baemin.com
F12를 눌러 개발자 창을 띄운 뒤 Ctrl + R을 누른다.
Page2로 넘어가서 바뀐 부분을 확인해본다
항목을 클릭하고 Preview 탭에서 내용 확인
Header에서 Request URL 확인
이렇게 하면 실제 URL을 확인 할 수 있다.
2. 응답의 형태
JSON
HTML
3. 요청의 형태
GET
POST
4. 요청시 사용하는 파라미터
파라미터 목록은 주로 Header 탭에 적혀있다.
Postman이라는 프로그램을 사용하여 api를 테스트 하고 Request URL을 붙여 넣어 필수 파라미터를 확인하는 방법이 있다.
REST란 무엇인가
Representational State Transfer의 약자로 자원을 이름으로 구분하여 자원의 정보를 주고 받는 모든 것을 말한다.
자원의 표현에 대한 상태 전달
자원의 표현: 문서 그림 데이터 등
상태 전달: JSON 혹은 XML로 데이터를 주고 받으며 데이터가 요청되어 지는 시점에서 전달한다.
장점
REST는 웹의 기존 기술과 HTTP 프로토콜을 그대로 활용하기 때문에 웹의 장점을 최대한 활용할 수 있는 아키텍처 스타일
- HTTP 프로토콜의 인프라 사용
- HTTP 프로토콜의 표준 활용
- HTTP 프로토콜을 준수하는 모든 플랫폼에서 사용
- REST API 메시지의 의도를 명확하게 확인이 가능
- 서버와 클라이언트의 역할 명시
단점
- 표준이 존재하지 않음
- 사용하는 Method는 4가지로 제한
- 구형 브라우저의 지원불가
구성요소
- 자원 (Resource): URI
- 행위 (Verb): HTTP Method
- 표현 (Representation of Resource)
상세
HTTP URI를 통해 자원을 명시함 그리고 HTTP Method(POST, GET, PUT, DELETE)를 통해 해당 자원의 CRUD Operation 적용을 함
CRUD Operation
- Create: POST
- Read: GET
- Update: PUT
- Delete: DELETE
- HEAD: header 정보 조회
REST API (Representational State Transfer API)
API
데이터와 기능의 집합을 제공하며 컴퓨터 프로그램간의 상호작용을 촉진. 이를 통해 정보의 교환이 가능
REST API: REST 기반 서비스 API 구현
특징
- 확장성과 재사용성
- HTTP 표준 기반 구현
- REST API 제작시 다양한 클라이언트 제작
다시 Retrofit의 사용법으로 돌아와서 사용법을 실제 확인하여 보고 어떻게 활용 할 수 있는 지 알아보자.
Retrofit을 사용하기 위해서는 다음의 순서가 필요하다
1. Retrofit이 필요한 라이브러리를 설치해 준다: 컴퓨터의 할일을 만들어주는 도구
2. Retrofit이 어떤 컴퓨터와 대화를 어떻게 할지 지정한다: 인터넷 주소와 대화 방식
3. Retrofit이 대화할 때 사용할 말과 들을 말을 정해준다: 데이터라는 이름의 작은 상자들을 만들어 그 안에 말을 적는다
4. Retrofit이 대화할 준비가 되면 Retrofit 객체라는 이름의 큰 상자를 만들어 인터넷 주소와 대화 방식 그리고 데이터 상자들을 넣는다.
5. Retrofit 객체의 큰 상자에서 원하는 데이터 상자를 꺼내어 다른 컴퓨터와 대화한다
이것을 다시 용어들을 사용해서 설명하면 다음과 같다.
1. pubspec.yaml 파일에 retrofit, json_annotation, dio 라이브러리를 추가합니다.
2. API를 정의할 인터페이스를 만들고 @RestApi 어노테이션을 붙입니다.
3. API를 호출할 메서드를 만들고 @GET, @POST, @PUT 등의 어노테이션을 붙입니다.
4. 요청과 응답에 사용할 모델 클래스를 만들고 @JsonSerializable 어노테이션을 붙입니다.
5. Retrofit 객체를 생성하고 인터페이스와 Dio 객체를 주입합니다.
6. Retrofit 객체의 메서드를 호출하여 API와 통신합니다.
해당 순서를 따라서 실제 예제를 만들고 따라해보자.
Retrofit 사용하기
패키지 추가
dependencies: flutter: sdk: flutter retrofit: ^2.0.1 json_annotation: ^4.0.1 cupertino_icons: ^1.0.2 dev_dependencies: flutter_test: sdk:flutter retrofit_generator: ^2.0.0+3 build_runner: ^2.1.2 json_serializable: ^4.1.4 |
Data Model 정의
API URL을 브라우저로 접속하여 응답 값 확인
API URL이란?
그럼 www.youtube.com/watch?v=dyRsYk0LyA8영상의 데이터를 가져오려면 id는 dyRsYk0LyA8로 하면 된다.
여기서, 구조를 잠시 본다면 다음과 같다.
https://www.googleapis.com/youtube/v3/videos?
id=VIDEO_ID&key=YOUR_API_KEY&fields=items(id,snippet(publishedAt,title,thumbnails),statistics(viewCount)&part=snippet,statistics
도출되는 API는 다음과 같을 것이다. (예시 참고: 서준수 brunch '플러터 Retrofit사용하기')
해당 응답값 에서 사용할 값에 대한 Data Model 생성
- 예) iid / snippet / publishedAt, title, thumbnails, statistics의 viewCount를 담는 모델 생성
- 주의 사항: 모델 생성시 변수 자료형과 변수명은 응답 값과 동일해야함
import 'package:json_annotation/json_annotation.dart'; // import package part 'DataModel.g.dart'; //인코딩과 디코딩 관련 코드 포함 g.dart 생성 class DatatModel {} @JsonSerializable() // Annotation 사용 class VideoItems { String id; VideoSnippet snippet; VideoStatistics statistics; VideoItems( {required this.id, required this.snippet, required this.statistics }); factory VideoItems.fromJson(Map<String, dynamic> json) => _$VideoItemsFromJson(json); // VideoItems는 클래스의 이름과 일치시켜줘야 한다 Map<String, dynamic> toJson() => _$VideoItemsToJson(this); } |
완성된 DataModel 클래스
import 'package:json_annotation/json_annotation.dart'; part 'DataModel.g.dart'; class DataModel {} @JsonSerializable() class VideoItems { String id; VideoSnippet snippet; VideoStatistics statistics; VideoItems( {required this.id, required this.snippet, required this.statistics}); factory VideoItems.fromJson(Map<String, dynamic> json) => _$VideoItemsFromJson(json); Map<String, dynamic> toJson() => _$VideoItemsToJson(this); } @JsonSerializable() class VideoSnippet { String publishedAt; String title; VideoThumbnail thumbnails; VideoSnippet( {required this.publishedAt, required this.title, required this.thumbnails}); factory VideoSnippet.fromJson(Map<String, dynamic> json) => _$VideoSnippetFromJson(json); Map<String, dynamic> toJson() => _$VideoSnippetToJson(this); } @JsonSerializable() class VideoThumbnail { // ThumbnailURL defaultURL; ThumbnailURL medium; ThumbnailURL high; VideoThumbnail({required this.medium, required this.high}); factory VideoThumbnail.fromJson(Map<String, dynamic> json) => _$VideoThumbnailFromJson(json); Map<String, dynamic> toJson() => _$VideoThumbnailToJson(this); } @JsonSerializable() class ThumbnailURL { String url; ThumbnailURL({required this.url}); factory ThumbnailURL.fromJson(Map<String, dynamic> json) => _$ThumbnailURLFromJson(json); Map<String, dynamic> toJson() => _$ThumbnailURLToJson(this); } @JsonSerializable() class VideoStatistics { String viewCount; VideoStatistics({required this.viewCount}); factory VideoStatistics.fromJson(Map<String, dynamic> json) => _$VideoStatisticsFromJson(json); Map<String, dynamic> toJson() => _$VideoStatisticsToJson(this); } |
요청하는 API (호출할 API의 정의)
- Retrofit은 기본 RestClient class와 Task class (=Data Model)이 같은 파일내에 있으나, 본 예제에서는 DataModel을 별도의 클래스로 지정했음으로 API 호출 관련 코드만 존재한다.
- 다음이 호출하는 코드의 전체다.
- 해당 형태 처럼 변경이 필요한 이유는 예제의 json이 List 형태이기 때문이다. Youtube API의 json의 경우는 처음에 키가 존재하며, List 형태를 띈다.
import 'package:retrofit/retrofit.dart'; import 'package:dio/dio.dart'; import 'DataModel.dart'; // import packages part 'RestClient.g.dart'; //Retrofit Generator에 의해 생성될 g.dart파일 @RestApi(baseUrl: "https://www.googleapis.com/youtube/v3") // API URL 중 일부분을 BaseUrl로 설정한다 abstract class RestClient { abstract class RestClient{ factory RestClient(Dio dio, {String baseUrl}) = _RestClient; // 해당 클래스는 추후 자동으로 생성이 된다. @GET("/videos") // API URL 나머지 부분 Future<Map<String, List<VideoItems>> getYouTube API ( // 응답 값은 item이라는 key값에 List가 존재하는 것이다 // API URL 중에 퀄리 부분을 설정하고 있다 @Query("id") String videoID, @Query("key") String devKey, @Query("fields") String fields, @Query("part") String part; ) } |
Generator 실행
g.dart 파일을 생성하기 위해서 다음 명령어를 터미널에 입력한다.
flutter pub run_runner build
: 해당 행위를 통해서 g.dart 파일들이 생성이 되고 기존 코드에서 에러로 표시되던 부분은 사라진다.
API 출 및 응답 데이터 사용
API 호출을 위해서는 RestClient에서 구현한 getYouTubeAPI 함수를 사용. 파라미터는 API URL에서 각 쿼리에 맞도록 입력한다.
getData() { final dio = Dio(); final client = RestClient(dio); client .getYouTubeAPI( "dyRsYk0LyA8" // youtube 영상의 id "YOUR_API_KEY", //발급받은 API KEY 삽입 "items(id, snippet(publishedAt, title,thumbnails), statistics(viewCount))", "snippet, statistics") .them((it){ setState(() { response = ((it['itmes'] as List)[0] as VideoItems; // Map에서 items의 키 값은 List isLoading = true; //응답을 받기 전에 초기화면을 보여주고 응답 받은 후의 데이터를 접근하기 위해서 이렇게 설정한다 log('${response.snippet.title};), }); }); } |
이제 간단한 UI를 구성하여 응답 받은 데이터중 나타내길 원하는 정보를 사용하여 화면상에 출력을 한다. 해당 예시에서는 썸네일, 타이틀, 조회수 정보를 나타낸다. (예시 참고: 서준수 brunch '플러터 Retrofit사용하기')
해당 결과 예시에서 볼 수 있는 것처럼 왼쪽화면에는 thumbnails에 초기 화면이 보인다.
그 옆 오른쪽에는 title이 보이며, 그 아래에는 viewCount까지 전부 보일 수 있도록 설정이 된다.
1~5까지의 전체 코드는 다음과 같다.
import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; import 'package:retrofit_pkg/RestClient.dart'; import 'dart:developer'; import 'DataModel.dart'; void main() => runApp(new WidgetDemo()); class WidgetDemo extends StatefulWidget { @override State<StatefulWidget> createState() => WidgetDemoState(); } class WidgetDemoState extends State<WidgetDemo> { late VideoItems response; bool isLoading = false; @override Widget build(BuildContext context) { return new MaterialApp( title: 'Retrofit Demo', home: new Scaffold( appBar: new AppBar( title: const Text('Retrofit Demo'), ), body: getPage(), ), ); } getData() { final dio = Dio(); final client = RestClient(dio); client .getYouTubeAPI( "dyRsYk0LyA8", "AIzaSyAt89piPh4VaJoImjxpvLeyJabciVyrppM", "items(id,snippet(publishedAt,title,thumbnails),statistics(viewCount))", "snippet,statistics") .then((it) { setState(() { response = ((it['items'] as List)[0]) as VideoItems; isLoading = true; log('${response.snippet.title}'); }); }); } Widget getPage() { late Widget page; if (!isLoading) { page = page0(); getData(); } else { page = page1(); } return page; } Container page0() { return Container( alignment: Alignment.center, child: Text( 'Loading...', style: TextStyle(color: Colors.blue, fontSize: 30), ), ); } ListView page1() { return ListView( children: <Widget>[ ListTile( leading: Image.network(response.snippet.thumbnails.medium.url), title: Text(response.snippet.title), subtitle: Text('View : ${response.statistics.viewCount}'), trailing: Icon(Icons.favorite_border), onTap: () {}, ), ], ); } } |
참고
- 기본개념 https://todaycode.tistory.com/38
- 예제1(Retrofit) https://brunch.co.kr/@mystoryg/157
- 예제2(Retrofit2) https://brunch.co.kr/@mystoryg/149
- REST API란? https://gmlwjd9405.github.io/2018/09/21/rest-and-restful.html
'Flutter' 카테고리의 다른 글
6. GoRouter (0) | 2023.03.15 |
---|---|
5. Riverpod과 Provider (0) | 2023.03.15 |
3. JasonSerializable(JSON의 직렬화) (0) | 2023.03.14 |
2. 페이지네이션, 페이지 기반과 커서기반의 차이 (Pagination) (0) | 2023.03.14 |
1. 보안 인증과 권한 (Authentication & Authorization) (0) | 2023.03.13 |