Dio?
공식 문서에서는 Dio를 'A powerful Http client for Dart'라고 말한다. 즉, http처럼 서버와 통신을 하기 위한 패키지이다. 사용하기 쉽게 많은 기능을 제공하고 있고, 커스텀해서 사용할 수 있어 더 편리하고 알맞게 사용할 수 있다.
쉽게 말하면, Dio는 flutter에서 api통신을 할 때 필요한 라이브러리이다.
물론, Dio를 사용하지 않아도 api통신에 문제는 없으나, 그냥 깡코딩으로 하려면 api통신을 위해 구현 시간이 경우에 따라 오래 걸린다. 따라서 개발 시간을 단축하면서도 좀 더 탄탄한 구성을 만들 수 있게 한다.
현재 DIO 라이브러리는 pub.dev에서 좋아요 수가 5809개로, 신뢰도가 높고 인기 있는 라이브러리이다.
사용법
위 공식 사이트를 참고하고,
dependencies에 등록하고 다운하면 쉽게 이용할 수 있다.
몇 가지 기능들을 소개해보면 아래와 같다.
Request & Response
var dio = Dio();
// 첫번째 방법
final response = await dio.get('/test?id=12&name=wendu');
// 두번째 방법
final response = await dio.get('/test', queryParameters: {'id': 12, 'name': 'wendu'});
// 세번째 방법
final response = await dio.request(
'/test',
data: {'id':12,'name':'xx'},
options: Options(method:'GET'),
);
// post
final response = await dio.post('/test', data: {'id': 12, 'name': 'wendu'});
requset와 response는 위와 같이 요청 메소드와 url을 함께 작성해주면 된다.
각 메서드 별로 같이 넘길 수 있는 여러가지 파라미터들이 있다. 요청 방법에는 여러가지가 있는데, 위 코드처럼 쿼리로 넘길 수도 있고, body를 통해 넘길 수도 있다.
Options
var dio = Dio();
dio.options.baseUrl = 'https://www.xx.com/api';
dio.options.connectTimeout = 5000; //5s
dio.options.receiveTimeout = 3000;
var options = BaseOptions(
baseUrl: 'https://www.xx.com/api',
connectTimeout: 5000,
receiveTimeout: 3000,
);
Dio dio = Dio(options);
// BaseOptions 객체
BaseOptions({
String? method,
int? connectTimeout,
int? receiveTimeout,
int? sendTimeout,
String baseUrl = '',
Map<String, dynamic>? queryParameters,
Map<String, dynamic>? extra,
Map<String, dynamic>? headers,
ResponseType? responseType = ResponseType.json,
String? contentType,
ValidateStatus? validateStatus,
bool? receiveDataWhenStatusError,
bool? followRedirects,
int? maxRedirects,
RequestEncoder? requestEncoder,
ResponseDecoder? responseDecoder,
ListFormat? listFormat,
this.setRequestContentTypeWhenNoPayload = false,
})
dio 객체를 생성하면 공통적으로 사용하고 싶은 것들을 BaseOptions를 통해 지정할 수 있다. 자주 사용되는 옵션들은 아래와 같다.
baseUrl : 요청할 기본 주소를 설정
connectTimeout : 서버로부터 응답받는 시간을 설정
receiveTimeout : 파일 다운로드 등과 같이 연결 지속 시간을 설정
headers : 요청 header 데이터를 설정
옵션의 경우에는 각 요청마다 설정도 가능하고 처음 dio객체를 생성할 때 설정도 가능하다. 공통부분은 dio 생성시에 설정하고, 나머지는 요청에 맞게 설정하는 것을 추천한다.
Interceptor
dio의 가장 강력한 부분 중 하나이다.
Interceptor는 요청때마다 가로채는 역할을 하는데, Interceptor를 통해 요청때마다 반복적인 작업을 처리할 수 있다. 토큰의 유효성을 검사하거나 로그를 처리하거나 등등.
또한, Interceptor에서는 3가지 메서드가 있다. 각각 아래와 같이 요청, 응답, 에러가 발생했을 때 동작을 처리할 수 있다.
class Interceptor {
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) =>
handler.next(options);
void onResponse(
Response response,
ResponseInterceptorHandler handler,
) =>
handler.next(response);
void onError(
DioError err,
ErrorInterceptorHandler handler,
) =>
handler.next(err);
}
Lock/unlock the interceptors
인터셉터를 통해 요청을 lock/unlock 할 수 있다. 공식 문서에 따르면, 이런 경우에는 대기열에 추가되어 인터셉터가 unlock이 될 때까지 대기하게 된다고 한다.
dio.interceptors.add(InterceptorsWrapper(
onRequest: (Options options, handler) async {
print('send request:path:${options.path},baseURL:${options.baseUrl}');
if (csrfToken == null) {
print('no token,request token firstly...');
//lock the dio.
dio.lock();
tokenDio.get('/token').then((d) {
options.headers['csrfToken'] = csrfToken = d.data['data']['token'];
print('request token succeed, value: ' + d.data['data']['token']);
print( 'continue to perform request:path:${options.path},baseURL:${options.path}');
handler.next(options);
}).catchError((error, stackTrace) {
handler.reject(error, true);
}) .whenComplete(() => dio.unlock()); // unlock the dio
} else {
options.headers['csrfToken'] = csrfToken;
handler.next(options);
}
}
));
위의 코드는 토큰의 유무를 검사하는 예시 코드이다. 인터셉터를 통해 요청때마다 토큰의 유무를 검사해 만약 토큰이 벗다면 새로운 토큰을 요청해 다시 이후에 요청을 진행하게 된다. 이 때 토큰이 없다면, 인터셉터를 통해 lock해서 대기열에 넣은 후 토큰을 받고 나서 다시 unlock하게 된다.
Resolve and reject the request
dio.interceptors.add(InterceptorsWrapper(
onRequest:(options, handler) {
return handler.resolve(Response(requestOptions:options,data:'fake data'));
},
));
Response response = await dio.get('/test');
print(response.data);
//'fake data'
모든 요청에 대해 응답을 제어할 수 있는데, 위의 코드처럼 모든 요청에 대해 fake data를 반환하는 것이 하나의 예시이다.
Log
// 기본 Log
dio.interceptors.add(LogInterceptor());
// CustomLog
dio.interceptors.add(CustomLogInterceptor());
class CustomLogInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
print('REQUEST[${options.method}] => PATH: ${options.path}');
super.onRequest(options, handler);
}
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
print(
'RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}',
);
super.onResponse(response, handler);
}
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
print(
'ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}',
);
super.onError(err, handler);
}
}
Log 지정도 가능하다. dio에서 기본적으로 제고아는 로그를 사용할 수 있다. 커스텀의 경우에는 Interceptor 객체를 상속받아 각 메서드를 구현해주면 된다.