별 헤는 밤 앱의 IOS 진출을 위해 Flutter 변환 작업을 하고 있습니다.
그 중 오늘은 앱에서 서버 데이터를 호출하는데 필요한 Retrofit에 대해 얘기 할 계획입니다.
RESTful API
Retrofit에 대해 설명하려면 RESTful API에 대해 먼저 설명할 필요가 있습니다.
REST(Representational State Transfer)는 클라이언트와 서버 간의 통신을 위한 소프트웨어 아키텍처 스타일을 의미합니다.
RESTful API은 이런 REST 아키텍처 스타일을 따르는 API로 주요 요소는 다음과 같습니다.
- 자원: HTTP URI를 통해 웹 서비스의 글, 유저, 이미지 등을 가져옵니다.
- 행위: HTTP Method를 사용하여 자원에 대한 행위를 나타냅니다. (ex) POST, GET, PUT, DELETE
- 표현: 서버와 클라이언트 간에 자원을 주고 받을 때 사용되는 데이터 형식을 의미(주로 JSON, XML 등이 있음)
Retrofit
Retrofit은 안드로이드 앱이 RESTful API 통신을 할 때 사용하는 라이브러리를 의미합니다.
안드로이드에서 HTTP URI 형태로 메세지를 전송하면 서버에서 수신하여 JSON, XML 형태의 데이터로 답변을 주고 받을 수 있게 됩니다.
Flutter에서는 Retrofit 연결을 어떻게 하는지 지금부터 천천히 설명하도록 하겠습니다.
Dependencies 설정
Retrofit 관련 패키지들을 설정하기 위해 2가지를 추가해야 합니다.
dependencies:
retrofit: ">=3.0.0 <4.0.0"
build_runner: ^2.1.10
json_annotation: ^4.8.1
json_serializable: ^6.7.1
dio: ^4.0.6
dev_dependencies:
retrofit_generator: ">=4.0.0 <5.0.0" // required dart >=2.19
build_runner: '>=2.3.0 <4.0.0'
json_serializable: ^6.6.2
pubspec.yaml
pub.dev 에서 retrofit 패키지 최신 버전을 설정하면 됩니다.
(확인해보니 24년 3월에는 “≥4.0.0 < 4.0.0”이 최신 버전이었습니다.)
$ flutter pub add retrofit
혹은 Android Studio로 개발 시 하단에 있는 Terminal 창에 해당 명령어를 입력하면 최신 버전으로 설치하게 됩니다.
Retrofit 패키지 이외에 4가지 패키지를 더 추가한 것을 확인할 수 있는데요.
각각의 패키지를 살펴보면 다음과 같습니다.
- dio: Flutter 전용 HTTP 통신 패키지
- build_runner: dart 파일 생성 패키지
- json_annotation: Dart 클래스에서 JSON 직렬화와 관련된 주석을 정의하기 위한 패키지
- json_serializable: json_annotation에서 제공한 주석을 기반으로 Dart 클래스의 직렬화 및 역 직렬화 코드를 생성하는 패키지
모델 코드 구현
패키지 설정을 다 했다면 2번째 단계로 모델 코드를 구현해야 합니다.
가장 기본적인 예시로 유저에 대한 서버 코드를 다음과 같이 준비되어 있다고 가정하겠습니다.
public class User{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column
private String name;
@Column
private String age;
}
서버의 User.java
그러면 Flutter 에서 User.dart 파일을 생성하고, 내부 코드는 다음과 같이 작성합니다.
import 'package:json_annotation/json_annotation.dart';
part 'User.g.dart'; // 자동 생성 시킬 g.dart 파일 이름 -- 현재 파일 동일한 이름+g.dart(!중요)
@JsonSerializable()
class User {
final String? name;
final String? age;
User({required this.Name,
required this.age }); //생성자
factory User.fromJson(Map<String, dynamic> json) =>_$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
직렬화를 위해서 Class User 상단의 @JsonSerializable() 어노테이션을 추가합니다
(part ‘User.g.dart’는 생성된 직렬화 코드의 파일 이름입니다.)
아마 처음 코드를 작성할 때에는 part 부분이 붉은 줄이 나오는데, 아직 생성이 안된 파일을 언급한 것에서 발생한 자연스러운 현상이므로 당황할 필요 없습니다.
그리고 하단에 생성자와 FromJson, ToJson 코드를 추가하고 Android Studio 하단의 Terminal에 ‘flutter pub run build_runner build’ 명령어를 실행시켜주면 됩니다.
정상적으로 작동한 했다면 상단의 Succeeded outputs 로그가 나오면서 하단의 User.g.dart 파일이 생성될 것입니다.
part of 'User.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
User _$UserFromJson(Map<String, dynamic> json) => User(
name: json['name'] as String?,
age: json['age'] as String?,
);
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'name': instance.name,
'age': instance.age,
};
User.g.dart
만약 정상적으로 g.dart 파일이 생성되지 않았다면 다음과 같은 문제들이 원인일 것입니다.
- g.dart 파일 이름이 클래스 이름과 동일하지 않음.
- 코드를 저장하지 않고 build 코드 실행
이래도 오류가 난다면 flutter clean 후에 build 명령어를 실행시키면 해결이 됩니다.
flutter clean
flutter pub run build_runner clean
flutter pub get && flutter pub run build_runner build
RestClient
안드로이드 앱에서는 RESTful API를 호출하는 클라이언트 이름을 RetrofitClient로 선택했는데, 이유는 retrofit2.gson 라이브러리를 사용했기 때문입니다.
하지만 Flutter에서는 RetrofitClient란 용어를 사용하지 않고 일반적으로 RestClient.dart로 명하는데 본인이 헷갈리지 않는 명칭을 선택하면 될 것 같습니다.
안드로이드와 Flutter의 구조 차이를 정리하면
프레임워크 | 안드로이드 | Flutter |
구조 | RetrofitClient + Interface | RestClient.dart + RestClient.g.dart |
안드로이드에서는 Client와 인터페이스 파일을 별도로 제작해야 했는데, Flutter는 RestClient.dart 하나의 파일에 Client와 API 인터페이스가 들어갑니다.
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';
import 'User.dart';
part 'UserRestClient.g.dart';
@RestApi(baseUrl:'서버 IP 주소')
abstract class UserRestClient{
factory UserRestClient(Dio dio, {String? baseUrl}) =
_UserRestClient; //Client 호출 코드
//안드로이드 interface 코드와 동일
@GET("user/{userId}")
Future<User> getUser(@Path('userId')int userId);
@DELETE('user/{userId}')
Future<void> deleteUser(@Path('userId')int userId);
}
RestClient.dart
여기서 중요한 차이 점은 안드로이드에서는 Call<type> 형태로 서버에 쿼리문을 전송했지만, Flutter는 비동기 명령어인 Future<type> 형태로 쿼리문을 전송합니다.
그리고 Flutter는 충격적이게도 short, Long, Float 타입을 지원하지 않고, int와 Double 타입만 지원합니다. 따라서 Flutter에서 서버로 전송할 땐 Long으로 전달해도 Flutter에서 수신할 때 int로 받아야합니다.
동일하게 터미널에서 ‘flutter pub run build_runner build’ 명령어를 실행하면 UserRestClient.g.dart
생성될 것입니다.
RestClient.g.dart 코드와 Future ,async, await 비동기 방식을 설명하려면 후술해야할 것들이 많기 때문에 다음 글에서 설명하도록 하겠습니다.
정리
이번 글에서는 Retrofit에 대해서 알아보고, Flutter에 적용 방법과 구현 방식을 알아보았습니다.
다음 글에서 실질적으로 서버에서 데이터를 불러오는 예시를 통해 RestClient.g.dart와 UI에서 구현한 RESTful api 호출 방식을 설명하도록 하겠습니다.
참고
'별밤 일지 > 개발' 카테고리의 다른 글
[Flutter] macOS에서 vscode로 flutter 개발환경 구축하기 (0) | 2024.06.10 |
---|---|
[Java] JsonPath, ObjectMapper (0) | 2024.06.10 |
[Java] 리플렉션, ObjectMapper (2) | 2024.03.12 |
[Springboot] Test With Mockito, JUnit (4) | 2024.03.05 |
[Android.hardware.Sensor] 안드로이드 Sensor를 활용하여 방위각, 고도 구하기 (0) | 2024.02.20 |