ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [플러터 / 라이브러리] API 통신에 편리한 dio의 기능정리.
    개발일지/flutter 2021. 8. 21. 14:35

    안녕하세요 개발하는남자 개남입니다. 

    오늘 포스팅 해볼 주제는 flutter에서 api통신을 할 때 필요한 라이브러리인 dio에 대해서 정리하려고 이렇게 포스팅을 합니다. 물론 dio가 없어도 전혀 api통신에 문제가 없습니다. 필요에 따라 기능 구현을 해주면 되기 때문입니다. 

    하지만 그렇게 하려면 api 통신을 하기 위한 구현 시간이 다소 걸리기때문에 개발 선배님들께서 만들어주신 라이브러리를 통해 개발 시간을 단축하면서도 좀 더 탄탄하게 구성할 수 있는 장점이 있습니다. 

    라이브러리를 사용하면 발생되는 단점도 존재합니다만, (의존성 부분, 프로젝트의 성격에 맞게 수정의 어려움) 

    하지만 장점이 더 많기때문에 라이브러리는 사용하는 것을 추천드립니다.

     


    DIO 라이브러리는 pub.dev에서 좋아요 수 2579개(2021년08월21일 기준)를 받은 신뢰 높고 인기 있는 라이브러리라고 할 수 있습니다. 관심이 많다는 것은 그만큼 많이 사용해왔고 사람들의 feed와 contributer들의 활약으로 많이 개선되어 왔다는 증거겠지요.

     

    dio | Dart Package

    A powerful Http client for Dart, which supports Interceptors, FormData, Request Cancellation, File Downloading, Timeout etc.

    pub.dev

    사실 정리하고 뭐할 필요없이 위 pub.dev에 기술되어있는 문서만 봐도 어떻게 사용할 수 있겠다 감이 오시고 사용하시는 분들이 많을 것이라 생각됩니다. 

    이 글을 쓰게 된 목적은 개인적으로 정리가 필요했으며, 혹, 약간의 예제와 한글 문서가 있으면 도움이 되지 않을까? 하는 생각으로 정리하게 되었습니다. 그리고 제가 문서를 보면서 필요하겠다 싶은 부분만 발췌해서 작성한 것이라는 점 언급하고 정리 시작하겠습니다. 

     


    DIO 문서에 가장 첫번째 설명하는 글을 살펴보겠습니다.

    A powerful Http client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc.

    Interceptor와 Global configuration, FormData , Request Cancellation , Fire downloading , Timeout 등등의 기능들이 있다고 설명합니다. (아무래도 가장 강력한 기능이라고 생각하는 순으로 나열한게 아닌가?? 하고 생각됩니다.)

     

     

     

    기본 사용법 


    사용법은 아주 간단하게 사용할 수 있습니다.

    라이브러리 pubspec.yaml에 등록 및 다운로드 

    dependencies:
      dio: ^4.0.0
    import 'package:dio/dio.dart';
    void getHttp() async {
      try {
        var response = await Dio().get('http://www.google.com');
        print(response);
      } catch (e) {
        print(e);
      }
    }

    dio options를 활용하여 좀 더 디테일하게 설정하여 관리할 수 있습니다. 

    dio options 설정 방법에는 두 가지가 있습니다. 

     

    1. default config 방식

    var dio = Dio(); // with default Options
    
    // Set default configs
    dio.options.baseUrl = 'https://www.xx.com/api';
    dio.options.connectTimeout = 5000; //5s
    dio.options.receiveTimeout = 3000;

    2. BaseOptions instance 활용방식

    Dio dio = Dio(
      BaseOptions(
        baseUrl: 'https://www.xx.com/api',
        connectTimeout: 5000,
        receiveTimeout: 3000,
      )
    );

    위의 방식을 사용하면 간단한 http 통신을 활용한 데이터 요청/응답을 받을 수 있게 됩니다.

    connectTimeout과 receiveTimeout의 차이는 connectTimeout 의 경우는 서버로부터 응받을때까지의 시간을 의미하며 설정 시간을 초과할 경우 connectTimeout Exception 이 발생되게 됩니다. 
    receiveTimeout 의 경우 서버로 부터 응답을 스트리밍?으로 받는 중에 연결 지속 시간을 의미합니다. 
    연결 지속 시간이 초과될 경우 receiveTimeout Exception 이 발생되게 됩니다. ex) 파일 다운로드 

     

    Response Schema 

    {
      /// Response body. may have been transformed, please refer to [ResponseType].
      T? data;
      /// Response headers.
      Headers headers;
      /// The corresponding request info.
      Options request;
      /// Http status code.
      int? statusCode;
      String? statusMessage;
      /// Whether redirect 
      bool? isRedirect;  
      /// redirect info    
      List<RedirectInfo> redirects ;
      /// Returns the final real request uri (maybe redirect). 
      Uri realUri;    
      /// Custom field that you can retrieve it later in `then`.
      Map<String, dynamic> extra;
    }

    response는 generic을 통해 return file type을 지정해서 받을 수 있습니다. (단, 보내주는 api 쪽에서도 동일하게 맞춰주는 작업이 필요하겠지만요, 대게는 Map 타입을 많이 활용합니다.)

     

     

     

     

     

    Interceptors


    Interceptor를 활용하면 client 요청/응답/오류에 대한 핸들링이 가능해집니다. 

     

    요청에 대한 핸들링

    api 요청 전 authentication의 토큰 값을 확인하여 토큰 값이 없거나 만료되었을 때에 request를 보내기 전 token 먼전 재발급받아 요청하게 만들 수 있습니다. 

     

    응답에 대한 핸들링

    api를 통해 응답을 받아 공통적으로 처리해야 하는 부분을 선 처리 후 repository로 넘겨줄 수 있습니다. 

    ex) json.decode 처리

     

    오류에 대한 핸들링

    error에 대해서 메시지 처리를 한다거나, path에 따라 error발생 대신 mock 데이터를 return 해준다거나 하는 작업을 할 수 있습니다. 

     

    Interceptor 선언 방법에도 2가지 방법이 있습니다. 

     

    1.

    dio.interceptors.add(InterceptorsWrapper(
        onRequest:(options, handler){
         // Do something before request is sent
         return handler.next(options); //continue
         // If you want to resolve the request with some custom data,
         // you can resolve a `Response` object eg: return `dio.resolve(response)`.
         // If you want to reject the request with a error message,
         // you can reject a `DioError` object eg: return `dio.reject(dioError)`
        },
        onResponse:(response,handler) {
         // Do something with response data
         return handler.next(response); // continue
         // If you want to reject the request with a error message,
         // you can reject a `DioError` object eg: return `dio.reject(dioError)` 
        },
        onError: (DioError e, handler) {
         // Do something with response error
         return  handler.next(e);//continue
         // If you want to resolve the request with some custom data,
         // you can resolve a `Response` object eg: return `dio.resolve(response)`.  
        }
    ));

     

    2.

    import 'package:dio/dio.dart';
    class CustomInterceptors extends Interceptor {
      @override
      void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
        print('REQUEST[${options.method}] => PATH: ${options.path}');
        return super.onRequest(options, handler);
      }
      @override
      Future onResponse(Response response, ResponseInterceptorHandler handler) {
        print('RESPONSE[${response.statusCode}] => PATH: ${response.request?.path}');
        return super.onResponse(response, handler);
      }
      @override
      Future onError(DioError err, ErrorInterceptorHandler handler) {
        print('ERROR[${err.response?.statusCode}] => PATH: ${err.request.path}');
        return super.onError(err, handler);
      }
    }

    두 가지 모두 가능하며 보통 1번을 많이 사용하는 것 같습니다.

    (구글링을 해도 1번으로 많이 사용하더라는....)

     

     

     

     

    FormData 


    formData의 경우는 파일과 데이터를 동시에 보내거나 다중 파일 업로드를 구현할 때 사용하면 손쉽게 구현이 가능합니다. 

     

    1. 싱글 파일로 요청하기

    var formData = FormData.fromMap({
      'name': 'wendux',
      'age': 25,
      'file': await MultipartFile.fromFile('./text.txt',filename: 'upload.txt')
    });
    response = await dio.post('/info', data: formData);

    2. 멀티 파일로 요청하기

    FormData.fromMap({
      'files': [
        MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
        MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
      ]
    });

    이렇게 파일 업로드를 요청할 수 있으며 동시에 sendProgress 데이터 역시 받을 수 있습니다.

    response = await dio.post(
        //"/upload",
        'http://localhost:3000/upload',
        data: await FormData3(),
        onSendProgress: (received, total) {
          if (total != -1) {
            print((received / total * 100).toStringAsFixed(0) + '%');
          }
        },
      );

     

     

     

     

     

    Cancellation


    모든 요청에 대해 cancel 토큰을 활용하면 모든 요청에 대해서 요청이 취소가 됩니다. 

    CancelToken token = CancelToken();
    dio.get(url, cancelToken: token)
       .catchError((DioError err){
        if (CancelToken.isCancel(err)) {
          print('Request canceled! '+ err.message)
        }else{
          // handle error.
        }
       });
    // cancel the requests with "cancelled" message.
    token.cancel('cancelled');

    사실 cancellation에 대해서는 이해는 되지만 어느 때에 사용해야 할지 감이 오지 않습니다.

    아직 경험이 부족해서 ^^;; 하지만 알아두면 사용할 시점에 기억이 떠오를 수 있겠지요 ~!

     

     

     


    여기까지 dio에 대해서 알아보았습니다. 

    플러터에서 기본적으로 제공하는 httpClient 역시 모두 구현이 가능 하지만 아무래도 많은 시행착오가 발생할 수 있고 시간이라는 비용이 발생하기에 dio를 조금만 공부하면 큰 효과를 볼 수 있을 것 같습니다. 

    간단 문서 정리와 간단 예제를 나열한 것으로 포스팅을 마무리되지만 유튜브(개남)에서는 위 예제들을 직접 사용하여 어떻게 동작되는지 살펴볼 예정입니다. 궁금하신 분들은 개발하는남자 유튜브 채널 구독/알림 설정하시면,

    관련 영상이 올라오면 바로 확인하실 수 있을 것입니다. 

     

    댓글

Designed by Tistory.