HTTP ํต์ - dio๋ก API ํธ์ถํ๊ธฐ
์ฑ์ ๋๋ถ๋ถ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ ํ๋ฉด์ ๋ณด์ฌ์ค๋ค. ๋น๋๊ธฐ ๊ธ์์๋ Future.delayed๋ก ์๋ฒ ์์ฒญ์ ํ๋ด ๋๋๋ฐ, ์ด๋ฒ์ ์ง์ง ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ณธ๋ค. ์ด ๊ธ์ dio 5.x ๊ธฐ์ค์ด๊ณ , ์ฐ์ต์ฉ ๊ณต๊ฐ API(jsonplaceholder)๋ฅผ ์ด๋ค. ๋จผ์ ์ค์นํ๋ค.
flutter pub add dio์ฌ์ฉ์ ํ ๋ช ์ ์ ๋ณด๋ฅผ ๋ฐ์์์ ์ด๋ฆ์ ์ถ๋ ฅํ๋ ์ฝ๋๋ฅผ ์ง๋ณธ๋ค. ๋น๋๊ธฐ ๊ธ์ ์ต๊ด์ด ์์ง ์ ๋ถ์๋ค๋ฉด ์ด๋ ๊ฒ ์ฐ๊ธฐ ์ฝ๋ค.
import 'package:dio/dio.dart';
Future<void> main() async {
final dio = Dio();
final response = dio.get('https://jsonplaceholder.typicode.com/users/1');
print(response.data); // ์ฌ๊ธฐ์ ์๋ฌ๋จ
}์๋ํฐ์ ๋นจ๊ฐ ์ค์ด ๋ฌ๋ค.
The getter 'data' isn't defined for the type 'Future<Response<dynamic>>'.dio.get์ ์๋ต์ด ์๋๋ผ Future<Response>, ์ฆ "๋์ค์ ์๋ต์ด ๋์ฐฉํ๋ค"๋ ์ฝ์์ ๋๋ ค์ค๋ค. Future์๋ data๊ฐ ์๋ค.
ํด๊ฒฐ: await๋ก ์๋ต์ ๊บผ๋ธ๋ค
import 'package:dio/dio.dart';
Future<void> main() async {
final dio = Dio();
final response = await dio.get('https://jsonplaceholder.typicode.com/users/1');
print(response.statusCode); // 200
print(response.data['name']); // Leanne Graham
}await: ์๋ต์ด ๋์ฐฉํ ๋๊น์ง ์ด ์ค์์ ๊ธฐ๋ค๋ฆฐ๋ค. ์ด ํจ์์๋async๊ฐ ๋ถ์ด ์์ด์ผ ํ๋คresponse.statusCode: HTTP ์ํ ์ฝ๋๋ค.200์ด๋ฉด ์ฑ๊ณต์ด๋คresponse.data: ์๋ต ๋ณธ๋ฌธ์ด๋ค. ์๋ฒ๊ฐ JSON์ ์ฃผ๋ฉด dio๊ฐ ์๋์ผ๋กMap์ด๋List๋ก ๋ฐ๊ฟ์ ์ค๋ค. ๋ฌธ์์ด์ ์ง์ jsonDecodeํ ํ์๊ฐ ์๋ค
response.data์ ํ์
์ dynamic์ด๋ค. ์๋ํฐ๊ฐ ์์ ๋ญ๊ฐ ์๋์ง ๋ชจ๋ฅธ๋ค๋ ๋ป์ด๋ผ, response.data['name']์์ ์คํ๋ฅผ ๋ด๋ ์ปดํ์ผ์ ํต๊ณผํ๊ณ ์คํํด์ null์ด ๋์จ๋ค. ์ด ๋ฌธ์ ๋ ๋ค์์ ๋ชจ๋ธ ํด๋์ค๋ก ํผ๋ค.
Dio ํ ๋ฒ ์ค์ ํด์ ๊ณ์ ์ฐ๊ธฐ
์ฃผ์์ ์๋ถ๋ถ์ ๋งค๋ฒ ์ฐ๋ ๊ฑด ๋ฒ๊ฑฐ๋กญ๋ค. BaseOptions๋ก ๊ณตํต ์ค์ ์ ํ ๋ฒ ์ ํ๋ค.
final dio = Dio(BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
));
final response = await dio.get('/users/1'); // baseUrl ๋ค์ ๋ถ์baseUrl: ๋ชจ๋ ์์ฒญ ์ฃผ์์ ์๋ถ๋ถ์ด๋คconnectTimeout,receiveTimeout: ์ฐ๊ฒฐ๊ณผ ์๋ต์ ๊ธฐ๋ค๋ฆฌ๋ ์ต๋ ์๊ฐ์ด๋ค. ์ ํ์ง ์์ผ๋ฉด ์๋ฒ๊ฐ ์๋ตํ์ง ์์ ๋ ์ฌ์ฉ์๊ฐ ํ์ฐธ ๊ธฐ๋ค๋ฆฐ๋ค
Dio ๊ฐ์ฒด๋ ์ฑ ์ ์ฒด์์ ํ๋๋ฅผ ๋ง๋ค์ด์ ๊ณต์ ํ๋ ๊ฒ ๋ณดํต์ด๋ค. ์์ฒญํ ๋๋ง๋ค Dio()๋ฅผ ์๋ก ๋ง๋ค๋ฉด ์ค์ ๋ ๋งค๋ฒ ๋ค์ ํด์ผ ํ๋ค.
์ฟผ๋ฆฌ์ ๋ณธ๋ฌธ์ ๋ณด๋ผ ๋๋ ์ด๋ ๊ฒ ์ด๋ค.
// GET /users?page=1&limit=10
await dio.get('/users', queryParameters: {'page': 1, 'limit': 10});
// POST /users, ๋ณธ๋ฌธ์ data๋ฅผ JSON์ผ๋ก ๋ด์ ๋ณด๋
await dio.post('/users', data: {'name': 'ํ๊ธธ๋'});data์ Map์ ๋ฃ์ผ๋ฉด JSON์ผ๋ก ๋ฐ๊ฟ์ ๋ณด๋ธ๋ค.
์ฝ์ง ํฌ์ธํธ 1: 404์ธ๋ฐ ์๋ฌ๊ฐ ๋๋ค - dio๋ ์คํจ ์๋ต์ ์์ธ๋ก ๋์ง๋ค
์๋ ์ฌ์ฉ์๋ฅผ ์์ฒญํด๋ณด์.
final response = await dio.get('/users/9999');
print(response.data); // ์ฌ๊ธฐ๊น์ง ์ค์ง ๋ชปํจjsonplaceholder๋ ์๋ ์ฌ์ฉ์์ 404๋ฅผ ์ค๋ค. ์ด๋ dio๋ ์๋ต์ ๋๋ ค์ฃผ๋ ๋์ DioException์ ๋์ง๋ค. Dart์ http ํจํค์ง๋ 404๋ ๊ทธ๋ฅ ์๋ต์ผ๋ก ์ฃผ๊ณ statusCode๋ฅผ ์ง์ ํ์ธํ๊ฒ ํ๋๋ฐ, dio๋ 2xx๊ฐ ์๋๋ฉด ์์ธ๋ก ์ทจ๊ธํ๋ค. ๊ทธ๋์ try/catch๊ฐ ํ์ํ๋ค.
try {
final response = await dio.get('/users/9999');
print(response.data);
} on DioException catch (e) {
if (e.response?.statusCode == 404) {
print('์๋ ์ฌ์ฉ์์์');
} else {
print('์์ฒญ ์คํจ: ${e.type}');
}
}on DioException catch (e): dio๊ฐ ๋์ง ์์ธ๋ง ์ก๋๋ค. ๋น๋๊ธฐ ๊ธ์try/catch์ ์์ธ ํ์ ์ ์ง์ ํ ํํ๋คe.response?.statusCode: ์๋ฒ๊ฐ ์๋ต์ ์คฌ๋ค๋ฉด ์ํ ์ฝ๋๊ฐ ์๋ค. ์๋ฒ์ ์์ ๋ฟ์ง ๋ชปํ๋ค๋ฉดresponse๊ฐnull์ด๋ผ์?.๊ฐ ํ์ํ๋ค. null safety ๊ธ์์ ๋ณธ ๊ทธ ์ฐ์ฐ์๋คe.type: ์คํจํ ์ด์ ๋ค. ์ฐ๊ฒฐ ์๊ฐ ์ด๊ณผ(connectionTimeout), ์๋ฒ๊ฐ ์คํจ ์๋ต์ ์ค(badResponse), ์ธํฐ๋ท์ด ์์(connectionError) ๊ฐ์ ์ข ๋ฅ๋ก ๋๋๋ค
์์ ๊ธ์๋ DioError๊ฐ ๋ง์ด ๋ณด์ธ๋ค. 5.x๋ถํฐ DioException์ผ๋ก ๋ฐ๋์์ผ๋ ์ ์ฝ๋์์๋ DioException์ ์ด๋ค.
์๋ต์ ๋ชจ๋ธ ํด๋์ค๋ก ๋ฐ๊พธ๊ธฐ
response.data๊ฐ dynamic์ด๋ผ ์คํ๋ฅผ ์ปดํ์ผ๋ฌ๊ฐ ๋ชป ์ก๋ ๋ฌธ์ ๋ฅผ, ์๋ต์ ํด๋์ค๋ก ๋ฐ๊ฟ์ ํผ๋ค. ์์ฑ์ ๊ธ์์ ๋ณธ factory fromJson์ด ๋ฐ๋ก ์ด ์๋ฆฌ์ ์ฐ์ธ๋ค.
class User {
final int id;
final String name;
User({required this.id, required this.name});
factory User.fromJson(Map<String, dynamic> json) {
return User(
id: json['id'] as int,
name: json['name'] as String,
);
}
}
Future<User> fetchUser(int id) async {
final response = await dio.get('/users/$id');
return User.fromJson(response.data as Map<String, dynamic>);
}์ด์ ํธ์ถํ๋ ์ชฝ์์๋ user.name์ฒ๋ผ ์ด๋ค. user.nmae๋ก ์คํ๋ฅผ ๋ด๋ฉด ์๋ํฐ๊ฐ ๋ฐ๋ก ๋นจ๊ฐ ์ค์ ์น๋ค. fromJson์ ์ง์ ์ฐ๋ ๊ฒ ํ๋๊ฐ ๋์ด๋๋ฉด ๋ฒ๊ฑฐ๋ก์ด๋ฐ, ์ด๊ฑธ ์๋์ผ๋ก ๋ง๋ค์ด์ฃผ๋ ๋ฐฉ๋ฒ์ ๋ค์ ๊ธ์์ ๋ค๋ฃฌ๋ค.
๋ชฉ๋ก(List)์ map์ผ๋ก ํ๋์ฉ ๋ฐ๊พผ๋ค. ์ปฌ๋ ์
๊ธ์์ ๋ณธ .toList() ์ต๊ด์ด ์ฌ๊ธฐ์๋ ํ์ํ๋ค.
Future<List<User>> fetchUsers() async {
final response = await dio.get('/users');
final list = response.data as List;
return list
.map((json) => User.fromJson(json as Map<String, dynamic>))
.toList();
}์ฝ์ง ํฌ์ธํธ 2: ์๋ฎฌ๋ ์ดํฐ์์๋ง, ๋๋ ๋ฆด๋ฆฌ์ค์์๋ง ์ ๋๋ค
์ฝ๋๋ ๋ง๋๋ฐ ํต์ ์ด ์ ๋๋ ๊ฒฝ์ฐ๋ ๋๋ถ๋ถ ์ฝ๋ ๋ฐ์ ์ค์ ์ด๋ค.
- ์๋๋ก์ด๋ ๋ฆด๋ฆฌ์ค ๋น๋์์๋ง ์ ๋๋ค: ๋๋ฒ๊ทธ ์คํ์์๋ ๋๋๋ฐ ๋ฐฐํฌ์ฉ์ผ๋ก ๋น๋ํ๋ฉด ์ ๋๋ ๊ฒฝ์ฐ๋ค. ์ฑ์
AndroidManifest.xml์<uses-permission android:name="android.permission.INTERNET"/>๊ฐ ์๋์ง ํ์ธํ๋ค. ๋๋ฒ๊ทธ์ฉ ์ค์ ์๋ง ๋ค์ด ์๊ณ ์ ์ ์ค์ ์๋ ๋น ์ ธ ์์ ์ ์๋ค - ๋ด ์ปดํจํฐ์ ์๋ฒ์
localhost๋ก ์ ์์ด ์ ๋๋ค: ์๋๋ก์ด๋ ์๋ฎฌ๋ ์ดํฐ์๊ฒlocalhost๋ ์๋ฎฌ๋ ์ดํฐ ์๊ธฐ ์์ ์ด๋ค. ๋ด ์ปดํจํฐ๋10.0.2.2๋ก ์ ์ํ๋ค. iOS ์๋ฎฌ๋ ์ดํฐ๋localhost๊ฐ ๊ทธ๋๋ก ๋ด ์ปดํจํฐ๋ฅผ ๊ฐ๋ฆฌํจ๋ค - iOS์์
http://์ฃผ์๋ง ๋งํ๋ค: iOS๋ ์ํธํ๋์ง ์์httpํต์ ์ ๊ธฐ๋ณธ์ผ๋ก ๋ง๋๋ค. ์๋ฒ๋ฅผhttps๋ก ๋ฐ๊พธ๋ ๊ฒ ์ ์์ด๋ค
์ธํฐ์ ํฐ: ๋ชจ๋ ์์ฒญ์ ๊ณตํต์ผ๋ก ๋ผ์ด๋ค๊ธฐ
๋ก๊ทธ์ธ ํ ํฐ์ ๋ชจ๋ ์์ฒญ์ ๋ถ์ด๊ฑฐ๋ ๋ก๊ทธ๋ฅผ ๋จ๊ธฐ๊ณ ์ถ์ ๋, ์์ฒญ๋ง๋ค ์ฝ๋๋ฅผ ์ฐ์ง ์๊ณ ์ธํฐ์ ํฐ๋ฅผ ํ ๋ฒ ๋ฑ๋กํ๋ค.
dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
options.headers['Authorization'] = 'Bearer $token'; // ๋ชจ๋ ์์ฒญ์ ํ ํฐ
handler.next(options); // ๋ค์ ๋จ๊ณ๋ก ์งํ
},
onError: (DioException e, handler) {
print('์คํจ: ${e.requestOptions.path}');
handler.next(e);
},
),
);onRequest๋ ์์ฒญ์ ๋ณด๋ด๊ธฐ ์ง์ ์, onError๋ ์คํจํ์ ๋ ์คํ๋๋ค. ๋ง์ง๋ง์ handler.next(...)๋ฅผ ๋นผ๋จน์ผ๋ฉด ์์ฒญ์ด ๊ฑฐ๊ธฐ์ ๋ฉ์ถ๋ค.
Compose, SwiftUI์ ๋น๊ตํ๋ฉด
// Kotlin - Retrofit
interface UserApi {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Int): User
}
// JSON -> User ๋ณํ์ ์ปจ๋ฒํฐ๊ฐ ์๋์ผ๋ก ํด์ค// Swift - URLSession
let (data, _) = try await URLSession.shared.data(from: url)
let user = try JSONDecoder().decode(User.self, from: data)- ์์ฒญ ๋ณด๋ด๊ธฐ: Flutter
dio.get('/users/1')/ Kotlin Retrofit ์ธํฐํ์ด์ค ํจ์ / SwiftURLSession.shared.data - ๊ธฐ๋ค๋ฆฌ๊ธฐ: Flutter
await/ Kotlinsuspend/ Swiftawait - JSON์ ๊ฐ์ฒด๋ก: Flutter๋
fromJson์ ์ง์ ์์ฑ / Kotlin์ ์ปจ๋ฒํฐ๊ฐ ์๋ / Swift๋Codable+JSONDecoder๊ฐ ์๋ - ์คํจ ์ฒ๋ฆฌ: Flutter
DioException/ KotlinHttpException๋ฑ / Swiftthrows์๋ฌ - ๊ณตํต ์ฒ๋ฆฌ: Flutter ์ธํฐ์ ํฐ / Kotlin OkHttp ์ธํฐ์ ํฐ / Swift ์ง์ ๊ตฌ์ฑ
Retrofit์ด dio์ ๊ฐ์ฅ ๋ฎ์๋ค. ๋ ๋ค ์๋ฒ ํต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ณ , OkHttp์ ์ธํฐ์
ํฐ์ dio์ ์ธํฐ์
ํฐ๋ ๊ฐ๋
์ด ๊ฐ๋ค. ๋ค๋ง Retrofit๊ณผ Swift์ Codable์ JSON์ ๊ฐ์ฒด๋ก ์๋ ๋ณํํด์ฃผ๋ ๋ฐ๋ฉด, Dart์์๋ fromJson์ ์ง์ ์จ์ผ ํ๋ค๋ ๊ฒ ๊ฐ์ฅ ํฐ ์ฐจ์ด๋ค.
์ฐธ๊ณ : dio์ http ํจํค์ง
Dart์๋ ๋ ๊ฐ๋ฒผ์ด http ํจํค์ง๋ ์๋ค. ๊ฐ๋จํ ์์ฒญ๋ง ํ๋ค๋ฉด ์ถฉ๋ถํ๋ค. dio๋ ํ์์์, ์ธํฐ์
ํฐ, ์์ฒญ ์ทจ์, ํ์ผ ์
๋ก๋ ๊ฐ์ ๊ธฐ๋ฅ์ด ๊ธฐ๋ณธ์ผ๋ก ๋ค์ด ์์ด์, ์ฑ์ด ์ปค์ง์๋ก ํธํด์ง๋ค. ์ด ์๋ฆฌ์ฆ๋ dio๋ฅผ ๊ธฐ์ค์ผ๋ก ๊ฐ๋ค.
์ ๋ฆฌ
dio.get์Future<Response>๋ฅผ ๋๋ ค์ฃผ๋ฏ๋กawait๋ก ๊บผ๋ด์ผresponse.data๋ฅผ ์ธ ์ ์๋ค- JSON ์๋ต์ dio๊ฐ ์๋์ผ๋ก
Map์ด๋List๋ก ๋ฐ๊ฟ์ฃผ์ง๋ง ํ์ ์ดdynamic์ด๋ผ, ๋ชจ๋ธ ํด๋์ค์factory fromJson์ผ๋ก ๋ฐ๊ฟ์ ์ด๋ค Dio๋BaseOptions๋กbaseUrl๊ณผ ํ์์์์ ์ ํด์ ์ฑ ์ ์ฒด์์ ํ๋๋ฅผ ๊ณต์ ํ๋ค- dio๋ 2xx๊ฐ ์๋ ์๋ต์
DioException์ผ๋ก ๋์ง๋ฏ๋กtry/catch๋ก ๊ฐ์ธ๊ณ ,e.response?.statusCode๋e.type์ผ๋ก ์คํจ ์ด์ ๋ฅผ ๊ตฌ๋ถํ๋ค - ์ฝ๋๊ฐ ๋ง๋๋ฐ ํต์ ์ด ์ ๋๋ฉด ์๋๋ก์ด๋์ INTERNET ๊ถํ, ์๋ฎฌ๋ ์ดํฐ์
10.0.2.2, iOS์http์ฐจ๋จ์ ์์ฌํ๋ค - ํ ํฐ์ด๋ ๋ก๊ทธ์ฒ๋ผ ๋ชจ๋ ์์ฒญ์ ๊ณตํต์ธ ์ฒ๋ฆฌ๋ ์ธํฐ์ ํฐ์ ํ ๋ฒ๋ง ๋ฑ๋กํ๋ค
๋ค์ ๊ธ์์๋ fromJson์ ์ง์ ์ฐ๋ ์๊ณ ๋ฅผ ์ค์ฌ์ฃผ๋ json_serializable๊ณผ freezed๋ฅผ ๋ค๋ค๋ณด๊ฒ ๋ค.
