Flutter에서 Dio를 사용하여 HTTP 통신하기
2024. 4. 3. 22:08ㆍ카테고리 없음
Flutter에서 Dio를 사용하여 HTTP 통신하기 : 시작하기 전에
Flutter 애플리케이션에서 HTTP 통신을 효율적으로 처리하기 위해 Dio 라이브러리를 사용하는 방법을 알아보기 전에, Dio를 프로젝트에 추가하고 설정하는 몇 가지 필수 단계를 알아보자
1. Dio 패키지 추가하기
// terminal에 입력
flutter pub add dio
2. 기본 Dio 인스턴스 설정
- Dio 인스턴스를 생성하고, 기본 설정을 적용하는 것으로 시작. 이 설정에는 기본 URL, 타임아웃, 헤더 등이 포함될 수 있음
import 'package:dio/dio.dart';
void main() async {
Dio dio = Dio();
dio.options = BaseOptions(
baseUrl: "https://api.example.com", // 요청의 기본 URL
connectTimeout: 5000, // 연결 시간 초과 (밀리초)
receiveTimeout: 3000, // 응답 시간 초과 (밀리초)
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
);
}
- baseUrl : 모든 요청에 대한 기본 URL로 각 요청의 상대 경로와 결합된다
- connectTimeout, receiveTimeout: 네트워크 연결 및 응답에 대한 타임아웃 설정,(밀리초단위)
- headers: 모든 요청에 공통으로 적용되는 HTTP 헤더
3. Flutter - Dio : 인터셉터 추가
- Dio는 요청/응답 인터셉터를 추가하여 HTTP 요청 및 응답을 가로채고,
필요한 경우 수정하거나 추가 처리를 할 수 있게 해준다
ex) 요청 전에 로그를 찍기, 응답을 받은 후 데이터를 전처리하기 등
dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 요청 전처리
return handler.next(options); // 요청 계속 진행
},
onResponse: (response, handler) {
// 응답 처리
return handler.next(response); // 응답 계속 진행
},
onError: (DioError e, handler) {
// 에러 처리
return handler.next(e); // 에러 계속 진행
},
));
- Dio 인스턴스 설정을 완료한 후에는 이를 사용하여 다양한 HTTP 요청을 보낼 수 있다
인스턴스의 설정은 애플리케이션의 요구 사항에 따라 조정될 수 있으며,
이를 통해 효율적인 네트워크 통신을 구현할 수 있다
4. HTTP 통신 : GET 요청 보내기, 서버로부터의 응답 처리, 에러 핸들링
✓ URL의 Query String을 통해 서버에 데이터를 전달한다
✓ URL에 전송할 데이터가 포함되어 있기 때문에 길이에 제한이 있을 수 있다
✓ 브라우저 기록, 캐싱 등에 사용되므로 민감한 데이터를 전송하기엔 적합하지 않다
import 'package:dio/dio.dart';
Future<void> sendGetRequest() async {
var dio = Dio();
try {
// GET 요청
var response = await dio.get(
'https://example.com/data',
queryParameters: {'id': 12, 'name': 'xx'}
);
if (response.statusCode == 200) {
// 성공 처리
print('Response data: ${response.data}');
} else {
// 에러 처리
print('Error: ${response.statusCode}');
}
} catch (e) {
// 예외 처리
print('Exception occurred: $e');
}
}
- dio.get(url, queryParameters: params):
url : 요청을 보낼 서버의 주소.
queryParameters : URL에 추가될 쿼리 매개변수를 담은 맵, 이 맵의 키와 값은 URL의 쿼리 스트링으로 변환된다. - response : 서버로부터의 응답을 포함하는 객체
response.data는 서버가 보낸 데이터를 포함하고,
response.statusCode는 HTTP 응답 코드를 포함한다. - try-catch : 네트워크 요청 중 발생할 수 있는 예외 처리 네트워크 연결 문제, 타임아웃, 서버에서 반환된 에러 등을 적절히 처리할 수 있으며,
response.statusCode를 검사해서 HTTP 상태 코드에 따라 다른 처리를 할 수 있다.
예제 서버로 부터 목록을 가져오자
요구사항 : 성능을 위해 최초에는 목록을 소량만 가져오고 스크롤을 내리면 통신시 보내는 index를 통해 리스트를 계속해서 불러와라
// controller
final Repository repo = Repository();
//repository의 getList()에는 위의 설명을 통해 만들어진 서버와 통신하는 코드가 있다
RxList<ExampleModel> exampleList = <ExampleModel>[].obs;
RxBool maxMore = false.obs;
var requestStatus = RequestStatus.EMPTY.obs;
late int index;
// GET_LIST
Future<bool> getList() async {
maxMore.value = false;
requestStatus.value = RequestStatus.LOADING;
exampleList.clear();
index = 0;
try {
Map<String, dynamic>? response = await repo.getList(index);
if (response?["list"] != null) {
exampleList.addAll(response!["list"].map((i) => ExampleModel.fromJson(i)));
if (response["list"].length < 10) {
maxMore.value = true;
}
requestStatus.value = RequestStatus.SUCCESS;
} else {
requestStatus.value = RequestStatus.SUCCESS;
}
return true;
} catch (e) {
requestStatus.value = RequestStatus.ERROR;
return false;
}
}
// GET_MORE_LIST
Future<void> getMoreList() async {
requestStatus.value = RequestStatus.LOADINGMORE;
index++;
try {
Map<String, dynamic>? response = await repo.getNoti(index);
if (response != null && response["totalPage"] >= index) {
var newList = response["list"] as List;
if (newList.isNotEmpty) {
exampleList.addAll(newList.map((i) => ExampleModel.fromJson(i)));
if (newList.length < 10) {
maxMore.value = true;
}
}
}
requestStatus.value = RequestStatus.SUCCESS;
} catch (e) {
requestStatus.value = RequestStatus.ERROR;
// 에러 메시지 처리 혹은 사용자에게 피드백 제공
}
}
// list page
//...
final Controller controller = Get.find<Controller>();
final ScrollController _scrollController = ScrollController();
@override
void initState() {
// 초기값 설정
alarmController.getList();
// 스크롤 추적해서 loading more
_scrollController.addListener(_handleScrollEvent);
super.initState();
}
@override
void dispose() {
_scrollController.removeListener(_handleScrollEvent);
_scrollController.dispose();
super.dispose();
}
void _handleScrollEvent() {
if (_isCloseToBottom(_scrollController) && _shouldLoadMore()) {
controller.getMoreList();
}
}
bool _isCloseToBottom(ScrollController controller) {
return controller.position.pixels >= controller.position.maxScrollExtent - 350;
}
bool _shouldLoadMore() {
return controller.requestStatus.value != RequestStatus.LOADINGMORE &&
controller.requestStatus.value != RequestStatus.LOADING &&
controller.requestStatus.value != RequestStatus.EMPTY &&
controller.maxMore.value == false;
}
//...
child: ListView.builder(
itemCount: controller.exampleList.length,
shrinkWrap: true,
controller: _scrollController,
physics: BouncingScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return exampleList[index]['example']
}
)
- 설명
- 초기 데이터 로드: initState에서 controller.getList()를 호출하여 초기 데이터를 로드한다
- 스크롤 리스너 설정: initState에서 _scrollController에 스크롤 리스너 _handleScrollEvent를 추가한다.
이 리스너는 사용자가 리스트의 끝에 도달할 때 추가 데이터를 불러오는 기능을 담당한다. - 스크롤 리스너 해제 및 컨트롤러 정리: dispose 메서드에서 스크롤 리스너를 제거하고 _scrollController를 정리한다.
- 스크롤 이벤트 처리: _handleScrollEvent 메서드는 스크롤 위치를 확인하고 _isCloseToBottom 및 _shouldLoadMore 조건을 평가하여 추가 데이터를 불러올지 결정한다
- ListView.builder 구성: ListView.builder는 controller.exampleList의 데이터를 기반으로 항목을 동적으로 생성한다.
- fromJson(i)에 대한 설명은 다음 포스팅에서 설명한다 (JSON 직렬화)