Flutter dio 패키지로 네트워크 요청 처리하기 🚀
안녕하세요, 여러분! 오늘은 Flutter 개발자들의 필수 무기, dio 패키지에 대해 깊이 파헤쳐볼 거예요. 네트워크 요청? 어렵지 않아요! dio와 함께라면 말이죠. 😉
먼저, dio가 뭔지 간단히 설명드릴게요. dio는 Flutter에서 HTTP 통신을 쉽게 할 수 있게 해주는 강력한 패키지예요. REST API 호출, 파일 다운로드, 폼 데이터 전송 등 네트워크와 관련된 거의 모든 작업을 손쉽게 처리할 수 있답니다. 마치 재능넷에서 다양한 재능을 쉽게 거래할 수 있는 것처럼 말이에요! 🎨💼
알고 계셨나요? dio는 '신'을 뜻하는 그리스어에서 유래했대요. 네트워크 요청의 신이 되어보는 건 어떨까요? ㅋㅋㅋ
자, 이제 본격적으로 dio의 세계로 들어가볼까요? 준비되셨나요? 그럼 고고씽! 🏃♂️💨
1. dio 패키지 설치하기 📦
먼저, dio를 우리 프로젝트에 초대해야겠죠? pubspec.yaml 파일을 열고 dependencies 섹션에 다음 줄을 추가해주세요:
dependencies:
dio: ^5.1.1
그리고 터미널에서 다음 명령어를 실행하세요:
flutter pub get
짜잔! 🎉 이제 dio가 우리 프로젝트의 일원이 되었어요. 마치 재능넷에 새로운 재능이 등록된 것처럼 말이죠!
Tip: 항상 최신 버전의 dio를 사용하는 것이 좋아요. pub.dev에서 최신 버전을 확인하세요!
이제 dio를 사용할 준비가 끝났어요. 다음 단계로 넘어가볼까요? 😎
2. dio 인스턴스 생성하기 🛠️
dio를 사용하려면 먼저 dio 인스턴스를 생성해야 해요. 이건 마치 재능넷에서 계정을 만드는 것과 비슷하답니다! 😉
다음과 같이 dio 인스턴스를 생성할 수 있어요:
import 'package:dio/dio.dart';
final dio = Dio();
와우! 정말 간단하죠? 하지만 이게 끝이 아니에요. dio 인스턴스에 다양한 옵션을 설정할 수 있답니다.
예를 들어, 기본 URL을 설정하거나 타임아웃 시간을 지정할 수 있어요:
final dio = Dio(BaseOptions(
baseUrl: 'https://api.example.com',
connectTimeout: Duration(seconds: 5),
receiveTimeout: Duration(seconds: 3),
));
이렇게 하면 모든 요청에 대해 기본 URL과 타임아웃 설정이 적용돼요. 편리하죠? 😊
Pro Tip: 글로벌 dio 인스턴스를 만들어 앱 전체에서 사용하면 코드 중복을 줄일 수 있어요! 마치 재능넷에서 한 번의 로그인으로 모든 서비스를 이용할 수 있는 것처럼요.
자, 이제 dio 인스턴스가 준비되었어요. 다음은 실제로 네트워크 요청을 보내볼 차례예요. 기대되지 않나요? 🤩
3. GET 요청 보내기 🚀
이제 진짜 재미있는 부분이 시작됩니다! GET 요청을 보내볼 거예요. GET 요청은 서버에서 데이터를 가져올 때 사용해요. 마치 재능넷에서 다양한 재능들을 검색하는 것과 비슷하죠! 😄
기본적인 GET 요청은 이렇게 보낼 수 있어요:
try {
final response = await dio.get('https://api.example.com/data');
print(response.data);
} catch (e) {
print('Error: $e');
}
와! 정말 간단하죠? 하지만 이게 다가 아니에요. 쿼리 파라미터를 추가하고 싶다면 어떻게 해야 할까요?
try {
final response = await dio.get(
'https://api.example.com/search',
queryParameters: {'keyword': 'flutter', 'page': 1},
);
print(response.data);
} catch (e) {
print('Error: $e');
}
이렇게 하면 URL이 'https://api.example.com/search?keyword=flutter&page=1'로 만들어져요. 꼭 필요한 기능이죠? 😎
주의: 항상 try-catch 문을 사용해 에러를 처리하세요! 네트워크 요청은 언제나 실패할 수 있어요. 마치 재능넷에서 거래할 때 항상 안전거래를 이용하는 것처럼, 에러 처리는 필수랍니다.
그런데 말이죠, GET 요청만으로는 부족할 때가 있어요. 다음은 POST 요청에 대해 알아볼까요? 🤔
4. POST 요청 보내기 📮
POST 요청은 서버에 데이터를 보낼 때 사용해요. 예를 들어, 새로운 사용자를 등록하거나 게시글을 작성할 때 사용하죠. 재능넷에서 새로운 재능을 등록하는 것과 비슷해요! 🎨
기본적인 POST 요청은 이렇게 보낼 수 있어요:
try {
final response = await dio.post(
'https://api.example.com/users',
data: {'name': 'John Doe', 'email': 'john@example.com'},
);
print(response.data);
} catch (e) {
print('Error: $e');
}
간단하죠? 하지만 때로는 더 복잡한 데이터를 보내야 할 때도 있어요. 예를 들어, JSON 데이터를 보내고 싶다면:
try {
final response = await dio.post(
'https://api.example.com/posts',
data: {
'title': 'Flutter로 앱 개발하기',
'content': 'Flutter는 정말 재미있어요!',
'tags': ['flutter', 'mobile', 'development']
},
options: Options(headers: {'Content-Type': 'application/json'}),
);
print(response.data);
} catch (e) {
print('Error: $e');
}
와우! 이제 JSON 데이터도 보낼 수 있게 되었어요. 👏
Tip: POST 요청을 보낼 때는 항상 데이터의 형식을 확인하세요. 서버가 어떤 형식의 데이터를 기대하는지 알아야 해요. JSON? Form data? 헷갈리지 마세요!
자, 이제 GET과 POST 요청을 마스터했어요. 하지만 아직 더 많은 것들이 남아있어요. 다음은 뭘 배워볼까요? 🤔
5. 파일 업로드하기 📁
여러분, 이제 정말 흥미진진한 부분이 왔어요! 파일 업로드에 대해 알아볼 거예요. 이건 재능넷에서 포트폴리오를 올리는 것과 비슷하답니다. 😉
dio를 사용하면 파일 업로드가 정말 쉬워져요. 한번 볼까요?
import 'dart:io';
try {
String fileName = 'my_awesome_file.png';
String filePath = '/path/to/your/file.png';
FormData formData = FormData.fromMap({
"name": "wendux",
"age": 25,
"file": await MultipartFile.fromFile(filePath, filename: fileName),
});
Response response = await dio.post(
"https://api.example.com/upload",
data: formData,
onSendProgress: (int sent, int total) {
print("$sent / $total");
},
);
print(response.data);
} catch (e) {
print('Error: $e');
}
우와! 😲 이게 바로 파일 업로드예요. 생각보다 복잡하지 않죠?
여기서 주목할 점은:
- FormData를 사용해 파일과 다른 데이터를 함께 보낼 수 있어요.
- MultipartFile.fromFile()을 사용해 파일을 추가해요.
- onSendProgress 콜백을 사용해 업로드 진행 상황을 추적할 수 있어요.
Pro Tip: 큰 파일을 업로드할 때는 항상 진행 상황을 사용자에게 보여주세요! 사용자는 기다리는 것을 싫어하지만, 진행 상황을 알면 참을성 있게 기다릴 수 있답니다. 😊
파일 업로드, 어렵지 않죠? 이제 여러분도 재능넷에 멋진 포트폴리오를 올릴 수 있을 거예요! 🎉
자, 이제 dio의 더 고급 기능들을 살펴볼까요? 다음 섹션에서 계속됩니다! 🚀
6. 인터셉터 사용하기 🕵️♂️
자, 이제 dio의 숨겨진 보물 같은 기능을 소개할게요. 바로 인터셉터(Interceptor)예요! 이름부터 멋지죠? ㅋㅋㅋ
인터셉터는 요청이 서버로 가기 전, 또는 응답이 우리 앱에 도착하기 전에 그것들을 가로채서 수정할 수 있게 해줘요. 마치 재능넷에서 거래 중개자 역할을 하는 것과 비슷해요! 😎
인터셉터를 사용하는 방법을 볼까요?
class MyInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// 요청이 보내지기 전에 실행됩니다.
print('REQUEST[${options.method}] => PATH: ${options.path}');
return super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
// 응답을 받은 후 실행됩니다.
print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
return super.onResponse(response, handler);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
// 에러가 발생했을 때 실행됩니다.
print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
return super.onError(err, handler);
}
}
// 인터셉터를 dio 인스턴스에 추가합니다.
dio.interceptors.add(MyInterceptor());
와우! 이제 모든 요청과 응답을 로깅할 수 있게 되었어요. 디버깅이 훨씬 쉬워질 거예요! 👨💻
Cool Tip: 인터셉터를 사용해 모든 요청에 인증 토큰을 자동으로 추가할 수 있어요. 이렇게 하면 매번 수동으로 토큰을 추가하지 않아도 돼요. 편리하죠?
인터셉터의 또 다른 멋진 사용 예를 볼까요? JWT 토큰을 자동으로 추가하는 인터셉터를 만들어볼게요:
class JwtInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// JWT 토큰을 가져옵니다. (예: SharedPreferences에서)
String token = getToken();
options.headers["Authorization"] = "Bearer $token";
return super.onRequest(options, handler);
}
}
dio.interceptors.add(JwtInterceptor());
이제 모든 요청에 자동으로 JWT 토큰이 추가될 거예요. 꿀팁이죠? 🍯
인터셉터는 정말 강력한 도구예요. 여러분의 상상력이 닿는 곳까지 활용해보세요! 다음 섹션에서는 더 흥미로운 내용이 기다리고 있어요. 준비되셨나요? 🚀
7. 동시에 여러 요청 보내기 🏎️💨
여러분, 이제 진짜 빠른 걸 경험해볼 시간이에요! 동시에 여러 개의 요청을 보내는 방법을 알아볼 거예요. 이건 마치 재능넷에서 여러 재능을 한 번에 검색하는 것과 같아요! 😉
dio를 사용하면 Future.wait()를 이용해 여러 요청을 동시에 보낼 수 있어요. 한번 볼까요?
Future<void> fetchMultipleData() async {
try {
final responses = await Future.wait([
dio.get('https://api.example.com/users'),
dio.get('https://api.example.com/posts'),
dio.get('https://api.example.com/comments'),
]);
final users = responses[0].data;
final posts = responses[1].data;
final comments = responses[2].data;
print('Users: $users');
print('Posts: $posts');
print('Comments: $comments');
} catch (e) {
print('Error: $e');
}
}
와우! 😲 이렇게 하면 세 개의 요청이 동시에 시작되고, 모든 요청이 완료될 때까지 기다렸다가 결과를 한 번에 받을 수 있어요. 엄청 빠르죠?
Speed Tip: 동시에 너무 많은 요청을 보내면 서버에 부담이 될 수 있어요. 적절한 수의 요청만 동시에 보내세요. 마치 재능넷에서 한 번에 너무 많은 재능을 요청하면 안 되는 것처럼요! 😅
그런데 말이죠, 때로는 여러 요청 중 하나라도 실패하면 전체가 실패하는 게 아니라, 성공한 요청의 결과만이라도 받고 싶을 때가 있어요. 이럴 때는 어떻게 해야 할까요?
Future<void> fetchMultipleDataSafely() async {
final futures = [
dio.get('https://api.example.com/users').catchError((e) => null),
dio.get('https://api.example.com/posts').catchError((e) => null),
dio.get('https://api.example.com/comments').catchError((e) => null),
];
final responses = await Future.wait(futures);
responses.asMap().forEach((index, response) {
if (response != null) {
print('Request ${index + 1} succeeded: ${response.data}');
} else {
print('Request ${index + 1} failed');
}
});
}
이렇게 하면 각 요청이 실패해도 다른 요청은 계속 진행되고, 성공한 요청의 결과를 받을 수 있어요. 안전하고 효율적이죠? 👍
자, 이제 여러분은 동시에 여러 요청을 보내는 프로 개발자가 되었어요! 🎉 다음 섹션에서는 더 흥미진진한 내용이 기다리고 있어요. 준비되셨나요? 🚀
8. 요청 취소하기 🚫
여러분, 이번에는 정말 쿨한 기능을 배워볼 거예요. 바로 요청을 취소하는 방법이에요! 이건 마치 재능넷에서 실수로 잘못된 재능을 요청했을 때 취소하는 것과 비슷해요. 😅
dio에서는 CancelToken을 사용해 요청을 취소할 수 있어요. 어떻게 하는지 볼까요?
CancelToken cancelToken = CancelToken();
void makeRequest() async {
try {
Response response = await dio.get(
'https://api.example.com/data',
cancelToken: cancelToken,
);
print('Response: ${response.data}');
} catch (e) {
if (CancelToken.isCancel(e)) {
print('Request cancelled: ${e.message}');
} else {
print('Error: $e');
}
}
}
void cancelRequest() {
cancelToken.cancel('Request cancelled by user');
}
// 요청 보내기
makeRequest();
// 잠시 후 요청 취소하기
Future.delayed(Duration(seconds: 2), () {
cancelRequest();
});
와우! 😲 이제 우리는 요청을 보내고 나서도 마음이 바뀌면 언제든 취소할 수 있어요. 엄청 쿨하지 않나요?
Cool Tip: CancelToken을 여러 요청에 사용하면, 한 번에 여러 요청을 취소할 수 있어요. 마치 재능넷에서 여러 재능 요청을 한 번에 취소하는 것처럼요! 👌
그런데 말이죠, 때로는 요청이 너무 오래 걸릴 때 자동으로 취소하고 싶을 때가 있어요. 이럴 때는 어떻게 해야 할까요?
Future<void> makeRequestWithTimeout() async {
CancelToken cancelToken = CancelToken();
try {
Timer(Duration(seconds: 5), () {
if (!cancelToken.isCancelled) {
cancelToken.cancel('Timeout');
}
});
Response response = await dio.get(
'https://api.example.com/data',
cancelToken: cancelToken,
);
print('Response: ${response.data}');
} catch (e) {
if (CancelToken.isCancel(e)) {
print('Request cancelled: ${e.message}');
} else {
print('Error: $e');
}
}
}
이렇게 하면 요청이 5초 이상 걸리면 자동으로 취소돼요. 시간은 여러분의 필요에 따라 조절할 수 있어요. 편리하죠? 😎
자, 이제 여러분은 요청을 자유자재로 컨트롤할 수 있는 dio 마스터가 되었어요! 🎉 다음 섹션에서는 더 흥미진진한 내용이 기다리고 있어요. 준비되셨나요? 🚀
9. 에러 처리와 재시도 로직 구현하기 🔄
안녕하세요, dio 마스터들! 이번에는 실전에서 정말 유용한 스킬을 배워볼 거예요. 바로 에러 처리와 재시도 로직이에요. 이건 마치 재능넷에서 거래가 실패했을 때 다시 시도하는 것과 비슷해요! 😉
먼저, 기본적인 에러 처리부터 살펴볼까요?
try {
final response = await dio.get('https://api.example.com/data');
print('Success: ${response.data}');
} on DioError catch (e) {
if (e.response != null) {
print('Dio error!');
print('STATUS: ${e.response?.statusCode}');
print('DATA: ${e.response?.data}');
print('HEADERS: ${e.response?.headers}');
} else {
print('Error sending request!');
print(e.message);
}
}
이렇게 하면 DioError를 잡아서 자세한 에러 정보를 볼 수 있어요. 하지만 실제 앱에서는 이것만으로는 부족할 수 있어요. 재시도 로직을 추가해볼까요?
Future<Response> requestWithRetry(String url, {int maxRetries = 3}) async {
int attempts = 0;
while (attempts < maxRetries) {
try {
return await dio.get(url);
} on DioError catch (e) {
if (e.type == DioErrorType.connectTimeout ||
e.type == DioErrorType.receiveTimeout) {
attempts++;
print('Retry attempt $attempts');
await Future.delayed(Duration(seconds: 2 * attempts)); // 지수 백오프
} else {
rethrow;
}
}
}
throw Exception('Failed after $maxRetries attempts');
}
// 사용 예
try {
final response = await requestWithRetry('https://api.example.com/data');
print('Success: ${response.data}');
} catch (e) {
print('All attempts failed: $e');
}
와우! 😲 이제 우리 앱은 네트워크 오류가 발생해도 자동으로 재시도를 해요. 게다가 지수 백오프(exponential backoff) 전략을 사용해서 서버에 과도한 부하를 주지 않도록 했어요. 똑똑하죠?
Pro Tip: 모든 요청에 재시도 로직을 적용하고 싶다면, 인터셉터를 사용해보세요! 이렇게 하면 코드 중복을 피하고 모든 요청에 일관된 에러 처리를 적용할 수 있어요.
인터셉터를 사용한 재시도 로직은 이렇게 구현할 수 있어요:
class RetryInterceptor extends Interceptor {
int maxRetries;
RetryInterceptor({this.maxRetries = 3});
@override
Future onError(DioError err, ErrorInterceptorHandler handler) async {
if (err.type == DioErrorType.connectTimeout ||
err.type == DioErrorType.receiveTimeout) {
var retries = err.requestOptions.extra['retries'] ?? 0;
if (retries < maxRetries) {
retries++;
print('Retry attempt $retries');
err.requestOptions.extra['retries'] = retries;
await Future.delayed(Duration(seconds: 2 * retries));
return handler.resolve(await dio.fetch(err.requestOptions));
}
}
return super.onError(err, handler);
}
}
// 사용 예
dio.interceptors.add(RetryInterceptor(maxRetries: 3));
이렇게 하면 모든 요청에 자동으로 재시도 로직이 적용돼요. 정말 편리하죠? 😎
자, 이제 여러분의 앱은 네트워크 오류에 더욱 강해졌어요! 🦸♂️ 다음 섹션에서는 dio의 또 다른 강력한 기능을 살펴볼 거예요. 기대되지 않나요? 🚀
10. 캐싱 구현하기 💾
안녕하세요, dio 마스터들! 이번에는 정말 꿀팁을 알려드릴게요. 바로 캐싱이에요! 캐싱을 사용하면 같은 데이터를 여러 번 요청하지 않아도 돼서 앱의 성능이 크게 향상돼요. 마치 재능넷에서 자주 찾는 재능을 즐겨찾기에 추가하는 것과 비슷해요! 😉
dio에서 캐싱을 구현하는 방법을 살펴볼까요?
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
// 캐시 옵션 설정
final options = CacheOptions(
store: MemCacheStore(),
policy: CachePolicy.request,
hitCacheOnErrorExcept: [401, 403],
maxStale: const Duration(days: 7),
priority: CachePriority.normal,
cipher: null,
keyBuilder: CacheOptions.defaultCacheKeyBuilder,
allowPostMethod: false,
);
// dio 인스턴스에 캐시 인터셉터 추가
dio.interceptors.add(DioCacheInterceptor(options: options));
// 사용 예
try {
final response = await dio.get('https://api.example.com/data');
print('Response: ${response.data}');
print('From cache: ${response.extra['fromNetwork'] == false}');
} catch (e) {
print('Error: $e');
}
와우! 😲 이제 우리 앱은 자동으로 응답을 캐시하고, 필요할 때 캐시된 데이터를 사용해요. 네트워크 사용량도 줄이고, 응답 속도도 빨라졌어요!
Cool Tip: 캐시 정책을 CachePolicy.forceCache로 설정하면, 네트워크가 끊겨도 캐시된 데이터를 사용할 수 있어요. 오프라인 모드를 구현할 때 유용하죠!
그런데, 때로는 특정 요청에 대해서만 캐시를 사용하고 싶을 때가 있어요. 이럴 때는 어떻게 해야 할까요?
// 특정 요청에 대해 캐시 옵션 설정
final response = await dio.get(
'https://api.example.com/data',
options: Options(extra: {
'dio_cache_interceptor': CacheOptions(
policy: CachePolicy.forceCache,
maxStale: Duration(days: 1),
),
}),
);
print('Response: ${response.data}');
print('From cache: ${response.extra['fromNetwork'] == false}');
이렇게 하면 특정 요청에 대해서만 다른 캐시 정책을 적용할 수 있어요. 유연하죠? 😎
캐싱을 사용하면 앱의 성능이 크게 향상되고, 사용자 경험도 좋아져요. 특히 데이터 사용량이 제한된 모바일 환경에서는 정말 유용해요!
자, 이제 여러분은 dio의 캐싱 마스터가 되었어요! 🏆 다음 섹션에서는 dio의 마지막 비밀을 공개할 거예요. 준비되셨나요? 🚀
11. 마무리: dio의 베스트 프랙티스 🏆
여러분, 축하드려요! 🎉 이제 여러분은 dio의 진정한 마스터가 되었어요. 마지막으로, dio를 사용할 때 알아두면 좋은 베스트 프랙티스들을 정리해볼게요.
- 싱글톤 패턴 사용하기: 앱 전체에서 하나의 dio 인스턴스만 사용하세요. 이렇게 하면 설정을 일관되게 유지하고 리소스를 효율적으로 사용할 수 있어요.
- 타임아웃 설정하기: 모든 요청에 적절한 타임아웃을 설정하세요. 사용자가 무한정 기다리게 하면 안 돼요!
- 에러 핸들링: 모든 가능한 에러 상황에 대비하세요. 사용자에게 친절한 에러 메시지를 보여주는 것도 잊지 마세요.
- 인터셉터 활용하기: 로깅, 인증, 캐싱 등 공통적인 작업은 인터셉터를 사용해 처리하세요.
- 취소 토큰 사용하기: 긴 요청이나 더 이상 필요 없는 요청은 취소할 수 있도록 CancelToken을 사용하세요.
Final Tip: 항상 최신 버전의 dio를 사용하세요. 새로운 기능과 버그 수정이 계속해서 이루어지고 있어요!
자, 이제 여러분은 dio의 모든 것을 알게 되었어요. 이 강력한 도구로 무엇을 만들어낼지 정말 기대되네요! 🚀
dio를 마스터한 여러분은 이제 어떤 네트워크 요청도 두렵지 않을 거예요. 마치 재능넷에서 모든 재능을 자유자재로 다루는 것처럼 말이죠! 😉
여러분의 Flutter 개발 여정에 dio가 큰 도움이 되길 바랍니다. 항상 즐겁게 코딩하세요! Happy coding! 🎈🎊