Flutter에서 Dio를 사용하여 HTTP 통신하기

2024. 4. 3. 22:08카테고리 없음

Flutter에서 Dio를 사용하여 HTTP 통신하기 : 시작하기 전에

Flutter 애플리케이션에서 HTTP 통신을 효율적으로 처리하기 위해 Dio 라이브러리를 사용하는 방법을 알아보기 전에, Dio를 프로젝트에 추가하고 설정하는 몇 가지 필수 단계를 알아보자

1. Dio 패키지 추가하기

pub.dev

// 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']
    }
  )
  • 설명
    1. 초기 데이터 로드: initState에서 controller.getList()를 호출하여 초기 데이터를 로드한다
    2. 스크롤 리스너 설정: initState에서 _scrollController에 스크롤 리스너 _handleScrollEvent를 추가한다.
      이 리스너는 사용자가 리스트의 끝에 도달할 때 추가 데이터를 불러오는 기능을 담당한다.
    3. 스크롤 리스너 해제 및 컨트롤러 정리: dispose 메서드에서 스크롤 리스너를 제거하고 _scrollController를 정리한다.
    4. 스크롤 이벤트 처리: _handleScrollEvent 메서드는 스크롤 위치를 확인하고 _isCloseToBottom_shouldLoadMore 조건을 평가하여 추가 데이터를 불러올지 결정한다
    5. ListView.builder 구성: ListView.buildercontroller.exampleList의 데이터를 기반으로 항목을 동적으로 생성한다.
  • fromJson(i)에 대한 설명은 다음 포스팅에서 설명한다 (JSON 직렬화)