Important note: I'm sorry to announce one thing to you: I (@ wendux) will not be able to continue to maintain the dio library . I understand that as a popular Http request library, dio has a large number of users, and has formed a plug-in ecosystem of a certain scale based on dio. However, due to the fast update speed of dart/flutter and my limited personal energy, it is an unavoidable decision for me to give up maintenance.
Fortunately, after the iteration of four major versions, the whole core and architecture of dio has been relatively stable, and the secondary development is not difficult. You can make customized development after forking on the basis of the latest version of source code.
dio 中文简体
A powerful Http client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc.
dependencies:
dio: ^4.0.6
Already know Dio 3 and just want to learn about what's new in Dio 4? Check out the Migration Guide!
import 'package:dio/dio.dart';
void getHttp() async {
try {
var response = await Dio().get('http://www.google.com');
print(response);
} catch (e) {
print(e);
}
}
🎉 A curated list of awesome things related to dio.
Plugins | Status | Description |
---|---|---|
dio_cookie_manager | A cookie manager for Dio | |
dio_http2_adapter | A Dio HttpClientAdapter which support Http/2.0 | |
dio_smart_retry | Flexible retry library for Dio | |
http_certificate_pinning | Https Certificate pinning for Flutter | |
curl_logger_dio_interceptor | A Flutter curl-command generator for Dio. | |
dio_cache_interceptor | Dio HTTP cache interceptor with multiple stores respecting HTTP directives (or not) | |
dio_http_cache | A simple cache library for Dio like Rxcache in Android | |
pretty_dio_logger | Pretty Dio logger is a Dio interceptor that logs network calls in a pretty, easy to read format. |
Welcome to submit Dio's third-party plugins and related libraries here .
Performing a GET
request:
Response response;
var dio = Dio();
response = await dio.get('/test?id=12&name=wendu');
print(response.data.toString());
// Optionally the request above could also be done as
response = await dio.get('/test', queryParameters: {'id': 12, 'name': 'wendu'});
print(response.data.toString());
Performing a POST
request:
response = await dio.post('/test', data: {'id': 12, 'name': 'wendu'});
Performing multiple concurrent requests:
response = await Future.wait([dio.post('/info'), dio.get('/token')]);
Downloading a file:
response = await dio.download('https://www.google.com/', './xx.html');
Get response stream:
Response<ResponseBody> rs;
rs = await Dio().get<ResponseBody>(url,
options: Options(responseType: ResponseType.stream), // set responseType to `stream`
);
print(rs.data.stream); //response stream
Get response with bytes:
Response<List<int>> rs
rs = await Dio().get<List<int>>(url,
options: Options(responseType: ResponseType.bytes), // set responseType to `bytes`
);
print(rs.data); // List<int>
Sending FormData:
var formData = FormData.fromMap({
'name': 'wendux',
'age': 25,
});
var response = await dio.post('/info', data: formData);
Uploading multiple files to server by FormData:
var formData = FormData.fromMap({
'name': 'wendux',
'age': 25,
'file': await MultipartFile.fromFile('./text.txt', filename: 'upload.txt'),
'files': [
await MultipartFile.fromFile('./text1.txt', filename: 'text1.txt'),
await MultipartFile.fromFile('./text2.txt', filename: 'text2.txt'),
]
});
var response = await dio.post('/info', data: formData);
Listening the uploading progress:
response = await dio.post(
'http://www.dtworkroom.com/doris/1/2.0.0/test',
data: {'aa': 'bb' * 22},
onSendProgress: (int sent, int total) {
print('$sent $total');
},
);
Post binary data by Stream:
// Binary data
List<int> postData = <int>[...];
await dio.post(
url,
data: Stream.fromIterable(postData.map((e) => [e])), //create a Stream<List<int>>
options: Options(
headers: {
Headers.contentLengthHeader: postData.length, // set content-length
},
),
);
…you can find all examples code here.
You can create instance of Dio with an optional BaseOptions
object:
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;
// or new Dio with a BaseOptions instance.
var options = BaseOptions(
baseUrl: 'https://www.xx.com/api',
connectTimeout: 5000,
receiveTimeout: 3000,
);
Dio dio = Dio(options);
The core API in Dio instance is:
Future request(String path, {data,Map queryParameters, Options options,CancelToken cancelToken, ProgressCallback onSendProgress, ProgressCallback onReceiveProgress)
response = await dio.request(
'/test',
data: {'id':12,'name':'xx'},
options: Options(method:'GET'),
);
For convenience aliases have been provided for all supported request methods.
Future<Response> get(...)
Future<Response> post(...)
Future<Response> put(...)
Future<Response> delete(...)
Future<Response> head(...)
Future<Response> put(...)
Future<Response> path(...)
Future<Response> download(...)
Future<Response> fetch(RequestOptions)
The Options class describes the http request information and configuration. Each Dio instance has a base config for all requests maked by itself, and we can override the base config with [Options] when make a single request. The [BaseOptions] declaration as follows:
{
/// Http method.
String method;
/// Request base url, it can contain sub path, like: 'https://www.google.com/api/'.
String baseUrl;
/// Http request headers.
Map<String, dynamic> headers;
/// Timeout in milliseconds for opening url.
int connectTimeout;
/// Whenever more than [receiveTimeout] (in milliseconds) passes between two events from response stream,
/// [Dio] will throw the [DioError] with [DioErrorType.RECEIVE_TIMEOUT].
/// Note: This is not the receiving time limitation.
int receiveTimeout;
/// Request data, can be any type.
T data;
/// If the `path` starts with 'http(s)', the `baseURL` will be ignored, otherwise,
/// it will be combined and then resolved with the baseUrl.
String path='';
/// The request Content-Type. The default value is 'application/json; charset=utf-8'.
/// If you want to encode request body with 'application/x-www-form-urlencoded',
/// you can set [Headers.formUrlEncodedContentType], and [Dio]
/// will automatically encode the request body.
String contentType;
/// [responseType] indicates the type of data that the server will respond with
/// options which defined in [ResponseType] are `JSON`, `STREAM`, `PLAIN`.
///
/// The default value is `JSON`, dio will parse response string to json object automatically
/// when the content-type of response is 'application/json'.
///
/// If you want to receive response data with binary bytes, for example,
/// downloading a image, use `STREAM`.
///
/// If you want to receive the response data with String, use `PLAIN`.
ResponseType responseType;
/// `validateStatus` defines whether the request is successful for a given
/// HTTP response status code. If `validateStatus` returns `true` ,
/// the request will be perceived as successful; otherwise, considered as failed.
ValidateStatus validateStatus;
/// Custom field that you can retrieve it later in [Interceptor]、[Transformer] and the [Response] object.
Map<String, dynamic> extra;
/// Common query parameters
Map<String, dynamic /*String|Iterable<String>*/ > queryParameters;
/// [collectionFormat] indicates the format of collection data in request
/// options which defined in [CollectionFormat] are `csv`, `ssv`, `tsv`, `pipes`, `multi`,`multiCompatible`.
/// The default value is `multiCompatible`
late CollectionFormat collectionFormat;
}
There is a complete example here.
The response for a request contains the following information.
{
/// 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;
}
When request is succeed, you will receive the response as follows:
Response response = await dio.get('https://www.google.com');
print(response.data);
print(response.headers);
print(response.requestOptions);
print(response.statusCode);
For each dio instance, We can add one or more interceptors, by which we can intercept requests 、 responses and errors before they are handled by then
or catchError
.
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: `handler.resolve(response)`.
// If you want to reject the request with a error message,
// you can reject a `DioError` object eg: `handler.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: `handler.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: `handler.resolve(response)`.
}
));
Simple interceptor example:
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
void onResponse(Response response, ResponseInterceptorHandler handler) {
print('RESPONSE[${response.statusCode}] => PATH: ${response.requestOptions.path}');
super.onResponse(response, handler);
}
@override
Future onError(DioError err, ErrorInterceptorHandler handler) {
print('ERROR[${err.response?.statusCode}] => PATH: ${err.requestOptions.path}');
return super.onError(err, handler);
}
}
In all interceptors, you can interfere with their execution flow. If you want to resolve the request/response with some custom data,you can call handler.resolve(Response)
. If you want to reject the request/response with a error message, you can call handler.reject(dioError)
.
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'
Interceptor
can be executed concurrently, that is, all of the requests enter the interceptor at once, rather than executing sequentially. However, in some cases we expect that requests enter the interceptor sequentially like #590 。 Therefore, we need to provide a mechanism for sequential access(one by one) to interceptors and QueuedInterceptor
can solve this problem.
Because of security reasons, we need all the requests to set up a csrfToken in the header, if csrfToken does not exist, we need to request a csrfToken first, and then perform the network request, because the request csrfToken progress is asynchronous, so we need to execute this async request in request interceptor. The code is as follows:
var dio = Dio();
// dio instance to request token
var tokenDio = Dio();
String? csrfToken;
dio.options.baseUrl = 'http://www.dtworkroom.com/doris/1/2.0.0/';
tokenDio.options = dio.options;
dio.interceptors.add(QueuedInterceptorsWrapper(
onRequest: (options, handler) {
print('send request:path:${options.path},baseURL:${options.baseUrl}');
if (csrfToken == null) {
print('no token,request token firstly...');
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);
});
} else {
options.headers['csrfToken'] = csrfToken;
return handler.next(options);
}
},
);
You can clean the waiting queue by calling clear()
;
For complete codes click here.
You can set LogInterceptor
to print request/response log automaticlly, for example:
dio.interceptors.add(LogInterceptor(responseBody: false)); //开启请求日志
You can custom interceptor by extending the Interceptor/QueuedInterceptor
class. There is an example that implementing a simple cache policy: custom cache interceptor.
dio_cookie_manager package is a cookie manager for Dio.
When a error occurs, Dio will wrap the Error/Exception
to a DioError
:
try {
//404
await dio.get('https://wendux.github.io/xsddddd');
} on DioError catch (e) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx and is also not 304.
if (e.response != null) {
print(e.response.data)
print(e.response.headers)
print(e.response.requestOptions)
} else {
// Something happened in setting up or sending the request that triggered an Error
print(e.requestOptions)
print(e.message)
}
}
{
/// Response info, it may be `null` if the request can't reach to
/// the http server, for example, occurring a dns error, network is not available.
Response? response;
/// Request info.
RequestOptions? request;
/// Error descriptions.
String message;
DioErrorType type;
/// The original error/exception object; It's usually not null when `type`
/// is DioErrorType.DEFAULT
dynamic? error;
}
enum DioErrorType {
/// It occurs when url is opened timeout.
connectTimeout,
/// It occurs when url is sent timeout.
sendTimeout,
///It occurs when receiving timeout.
receiveTimeout,
/// When the server response, but with a incorrect status, such as 404, 503...
response,
/// When the request is cancelled, dio will throw a error with this type.
cancel,
/// Default error type, Some other Error. In this case, you can
/// use the DioError.error if it is not null.
other,
}
By default, Dio serializes request data(except String type) to JSON
. To send data in the application/x-www-form-urlencoded
format instead, you can :
//Instance level
dio.options.contentType= Headers.formUrlEncodedContentType;
//or works once
dio.post(
'/info',
data: {'id': 5},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
You can also send FormData with Dio, which will send data in the multipart/form-data
, and it supports uploading files.
var formData = FormData.fromMap({
'name': 'wendux',
'age': 25,
'file': await MultipartFile.fromFile('./text.txt',filename: 'upload.txt')
});
response = await dio.post('/info', data: formData);
There is a complete example here.
There are two ways to add multiple files to FormData
, the only difference is that upload keys are different for array types。
FormData.fromMap({
'files': [
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
MultipartFile.fromFileSync('./example/upload.txt', filename: 'upload.txt'),
]
});
The upload key eventually becomes 'files[]',This is because many back-end services add a middle bracket to key when they get an array of files. If you don't want “[]”,you should create FormData as follows(Don't use FormData.fromMap
):
var formData = FormData();
formData.files.addAll([
MapEntry('files',
MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
),
MapEntry('files',
MultipartFile.fromFileSync('./example/upload.txt',filename: 'upload.txt'),
),
]);
Transformer
allows changes to the request/response data before it is sent/received to/from the server. This is only applicable for request methods 'PUT', 'POST', and 'PATCH'. Dio has already implemented a DefaultTransformer
, and as the default Transformer
. If you want to customize the transformation of request/response data, you can provide a Transformer
by your self, and replace the DefaultTransformer
by setting the dio.transformer
.
If you use dio in flutter development, you'd better to decode json in background with [compute] function.
// Must be top-level function
_parseAndDecode(String response) {
return jsonDecode(response);
}
parseJson(String text) {
return compute(_parseAndDecode, text);
}
void main() {
...
//Custom jsonDecodeCallback
(dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
runApp(MyApp());
}
There is an example for customizing Transformer.
HttpClientAdapter is a bridge between Dio and HttpClient.
Dio implements standard and friendly API for developer.
HttpClient: It is the real object that makes Http requests.
We can use any HttpClient not just dart:io:HttpClient
to make the Http request. And all we need is providing a HttpClientAdapter
. The default HttpClientAdapter for Dio is DefaultHttpClientAdapter
.
dio.httpClientAdapter = new DefaultHttpClientAdapter();
Here is a simple example to custom adapter.
DefaultHttpClientAdapter
provide a callback to set proxy to dart:io:HttpClient
, for example:
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
...
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
// config the http client
client.findProxy = (uri) {
//proxy all request to localhost:8888
return 'PROXY localhost:8888';
};
// you can also create a new HttpClient to dio
// return HttpClient();
};
There is a complete example here.
There are two ways to verify the https certificate. Suppose the certificate format is PEM, the code like:
String PEM='XXXXX'; // certificate content
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==PEM){ // Verify the certificate
return true;
}
return false;
};
};
Another way is creating a SecurityContext
when create the HttpClient
:
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
SecurityContext sc = SecurityContext();
//file is the path of certificate
sc.setTrustedCertificates(file);
HttpClient httpClient = HttpClient(context: sc);
return httpClient;
};
In this way, the format of certificate must be PEM or PKCS12.
dio_http2_adapter package is a Dio HttpClientAdapter which support Http/2.0 .
You can cancel a request using a cancel token. One token can be shared with multiple requests. When a token's cancel
method invoked, all requests with this token will be cancelled.
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');
There is a complete example here.
Dio
is a abstract class with factory constructor,so we don't extend Dio
class directy. For this purpose, we can extend DioForNative
or DioForBrowser
instead, for example:
import 'package:dio/dio.dart';
import 'package:dio/native_imp.dart'; //If in browser, import 'package:dio/browser_imp.dart'
class Http extends DioForNative {
Http([BaseOptions options]):super(options){
// do something
}
}
We can also implement our Dio client:
class MyDio with DioMixin implements Dio{
// ...
}
This open source project authorized by https://flutterchina.club , and the license is MIT.
Please file feature requests and bugs at the issue tracker.
Buy a cup of coffee for me (Scan by wechat):