The low-level HTTP Client and a higher level implementation with the http package have been tested previously. This time, we will try another high level implementation of an HTTP client in Dart called dio. This client seems way more flexible than the previous clients, and embed great features. Furthermore, based on pub.dev, it looks like it's a popular choice. If you are interested to see some examples, you can look into example_dart/lib directory from their official repository. Anyway the first step to use it is to add it in the dependencies.
dart pub add dio
dart pub get
This time, instead of of using our own quick and dirty HTTP server using netcat or Python http.server module, let use the free HTTP Echo Server service. The idea is to simply creates some requests to this server and print the data returned.
As usual, the first step is to import the package in bin/httpcat.dart.
import 'package:dio/dio.dart';
Then, let create/configure a dio client object using the Dio class
final target = 'https://echo.free.beeceptor.com';
final dio = Dio(
BaseOptions(
baseUrl: target,
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 5),
contentType: null,
followRedirects: true,
maxRedirects: 2,
persistentConnection: false,
preserveHeaderCase: false,
// receiveDataWhenStatusError; ...
// requestEncoder: ...
// responseDecoder: ...
// responseType: ...
// sendTimeout: ...
// validateStatus: ...
headers: {
HttpHeaders.userAgentHeader: 'dio',
'x-custom': 'custom'
},
)
);
The Dio class constructor accepts a BaseOptions object, containing the options to configure for the HTTP client. As you can already see in the previous snippet, plenty of attributes are available:
baseUrl: set the URL target asString. It must be a valid URL;connectTimeout: the connection timeout out asint, when the client is opening a new connection to the remote server. Setting it tonullor toDuration.zerowill wait for the connection forever;contentType: set theContent-TypeHTTP header as a String;followRedirects: a Boolean value to define if the client should follow the redirection returned by the server;maxRedirects: the maximum number of redirection the client is allowed to follow defined asint;persistentConnection: define if the client should use a persistent connection or not as boolean;preserveHeaderCase: keep the header case unmodified during the request, defined as boolean;queryParameters: set the query parameters of the request asMap<String, dynamic>;receiveDataWhenStatusError: I will need to check the code for this one, but I assume when an error is returned, the data received is dropped, if we set this property totrue, we should then be able to receive the data from the error;receiveTimeout: set the timeout as aDurationfor the data sent by the server;requestEncoder: define a custom request encoder usingRequestEncodertype as function callback. A way to use it, like defined in the tests:
dio.options.requestEncoder = (data, _) => Future.value(utf8.encode(data));
// or
dio.options.requestEncoder = (data, _) => utf8.encode(data);
-
responseDecoder: define a custom response decoder usingResponseDecodertype as function callback. It can be used like the following snippet from the test suite.
dio.options.responseDecoder = (_, __, ___) => null;
// or
dio.options.responseDecoder = (_, __, ___) => Future.value('example');
// or
dio.options.responseDecoder = (_, __, ___) => 'example';
responseType: set the kind of response wanted by the client. For example, it can be set toResponseType.stream,ResponseType.plainorResponseType.bytes.sendTimeout: set the client send timeout, when data is being uploaded to the server;validateStatus: set a custom function callback defining if a request status is good or not, usingValidateStatustype. Here an example of usage from the test suite, thestatusparameter is an integer representing the HTTP status code.
validateStatus: (status) => true;
// or
validateStatus: (status) => false;
-
headers: configure aMap<String, dynamic>as HTTP Headers. Standardized HTTP Headers keys can be found inHttpHeadersclass, defined as constants, for exampleacceptEncodingHeadercontains the stringaccept-encoding. The code in charge of this part can be seen indio/lib/src/headers.dart, it contains some constants;
After having created a Dio object, one can use different way to execute a request, but usually, two conventions can be used, using the baseUrl attribute set in the object, or pass it an Uri object.
- GET Method
-
dio.get(path)wherepathis aString -
dio.getUri(uri)whereuriis anUriobject
-
- HEAD Method
-
dio.head(path)wherepathis aString -
dio.headUri(uri)whereuriis anUriobject
-
- POST Method
-
dio.post(path)wherepathis aString -
dio.postUri(uri)whereuriis anUriobject
-
- PUT Method
-
dio.put(path)wherepathis aString -
dio.putUri(uri)whereuriis anUriobject
-
- DELETE Method
-
dio.delete(path)wherepathis aString -
dio.deleteUri(uri)whereuriis anUriobject
-
- PATCH Method
-
dio.patch(path) where
pathis aString -
dio.patchUri(uri) where
uriis anUriobject
-
dio.patch(path) where
So, before starting, the main() function will be modified to add a switch statement, and then, easily test each method from the CLI.
Future<int> main(List<String> arguments) async {
if (arguments.length != 1) {
print("Usage: httpcat TARGET");
return 1;
}
switch (arguments[0]) {
case "get": return getRequest();
case "head": return headRequest();
case "post": return postRequest();
case "put": return putRequest();
case "delete": return deleteRequest();
case "patch": return patchRequest();
}
Uri? url = parseUrl(arguments[0]);
if (url == null) return 1;
var ret = getUrl(url);
return ret;
}
Then, the code will be called like that:
dart run ./bin/httpcat.dart get
Another quick modification is required to help seeing the answers from the server. Let create a function called printResponse() to print the data from the response.
void printResponse(Response response) {
print({
'status': response.statusCode,
'status_msg': response.statusMessage,
'uri': response.realUri,
'headers': response.headers,
'data': response.data,
});
}
It will simply print to STDOUT the attributes available in a Response object.
GET Method
Probably one of the most used HTTP request is the GET one.
The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity in the response and not the source text of the process, unless that text happens to be the output of the process.
getRequest() will use get() method to fetch /:
Future<int> getRequest() async {
var response = await dio.get('/');
printResponse(response);
return 0;
}
Let run this code to see what it will return from the CLI.
$ dart run bin/httpcat.dart get
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 12:39:47 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:38247, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Accept-Encoding: gzip, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}}}
The answer from the server is printed alongside with the status and other information. The return data is a JSON object, containing my - modified - IP address, the headers and so on.
HEAD Method
The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.
headerRequest() function will use head() method and print the response.
Future<int> headRequest() async {
var response = await dio.head('/');
printResponse(response);
return 0;
}
Let execute this function now.
$ dart run bin/httpcat.dart head
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
access-control-allow-origin: *
date: Wed, 13 May 2026 12:48:48 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: null}
No data is returned there, it's normal. HTTP Head method does not return any data, like defined in the specifications.
POST Method
The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. POST is designed to allow a uniform method to cover the following functions:
- Annotation of existing resources;
- Posting a message to a bulletin board, newsgroup, mailing list, or similar group of articles;
- Providing a block of data, such as the result of submitting a form, to a data-handling process;
- Extending a database through an append operation.
postRequest() function will use post() method from the client. A POST request can pass some data, to do that, the string test_data is passed to the data parameter. The contentType attribute must also be updated, because dio is intercepting the POST request and will set it to JSON. Let set this to text/plain.
Future<int> postRequest() async {
dio.options.contentType = 'text/plain';
var response = await dio.post(
'/',
data: 'test_data'
);
printResponse(response);
return 0;
}
We can now invoke the command:
$ dart run bin/httpcat.dart post
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:27:11 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: POST, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:40459, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/html, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}
As you can see, the data we have sent is stored in the rawBody field. POST, PUT and PATCH methods are really important, and another publication on this topic will be required, especially for the serialization part.
PUT Method
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI.
The PUT method is similar to the POST method. Here a putRequest() function is created, the content type is updated and the string test_data is sent to the server via the post() method.
Future<int> putRequest() async {
dio.options.contentType = 'text/plain';
var response = await dio.put(
'/',
data: 'test_data'
);
printResponse(response);
return 0;
}
Similar result.
$ dart run bin/httpcat.dart put
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:32:18 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: PUT, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:44886, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/plain, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}
DELETE Method
The DELETE method requests that the origin server delete the resource identified by the Request-URI. This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot be guaranteed that the operation has been carried out, even if the status code returned from the origin server indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, at the time the response is given, it intends to delete the resource or move it to an inaccessible location.
The DELETE method is closer to the GET method. deleteRequest() function is created, and the delete() method is called.
Future<int> deleteRequest() async {
var response = await dio.delete('/');
printResponse(response);
return 0;
}
Here the returned data:
$ dart run bin/httpcat.dart delete
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:32:49 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: DELETE, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:40520, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Accept-Encoding: gzip, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}}}
PATCH Method
The PATCH method requests that a set of changes described in the request entity be applied to the resource identified by the Request-URI. The set of changes is represented in a format called a "patch document" identified by a media type. If the Request-URI does not point to an existing resource, the server MAY create a new resource, depending on the patch document type (whether it can logically modify a null resource) and permissions, etc.
The PATCH method is a recent addition to HTTP, but act similarly like the PUT and POST methods. patchRequest() function is created, the content type is updated and patch() method is called.
Future<int> patchRequest() async {
dio.options.contentType = 'text/plain';
var response = await dio.patch(
'/',
data: 'test_data'
);
printResponse(response);
return 0;
}
Again, here the result:
$ dart run bin/httpcat.dart patch
{status: 200, status_msg: OK, uri: https://echo.free.beeceptor.com/, headers: connection: close
alt-svc: h3=":443"; ma=2592000
transfer-encoding: chunked
access-control-allow-origin: *
date: Wed, 13 May 2026 14:33:28 GMT
vary: Accept-Encoding
content-type: application/json
via: 1.1 Caddy
, data: {method: PATCH, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:34690, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Content-Length: 9, Accept-Encoding: gzip, Content-Type: text/plain, Via: 1.1 Caddy, X-Custom: custom}, parsedQueryParams: {}, rawBody: test_data}}
Downloading Files
dio is also offering two helpers to download file and store save locally via download() and downloadURI() methods. Let try to download the latest official OpenBSD banner locally using the downloadUri() method this time.
Future<int> downloadRequest() async {
final url = Uri.parse('https://www.openbsd.org/images/puffy78.gif');
var response = await dio.downloadUri(
url,
(_) => './puffy78.gif',
);
printResponse(response);
return 0;
}
downloadRequest() function is creating a new Uri object from a valid URL, in this case, from the OpenBSD website. Then, the url and an anonymous function is passed to the downloadUri() method. Finally, the response is displayed on STDOUT and the function return 0.
The anonymous function is taking one argument, a Headers object and must return a String representing where the file will be stored.
Let execute that:
$ dart run bin/httpcat.dart download
{status: 200, status_msg: OK, uri: https://www.openbsd.org/images/puffy78.gif, headers: content-type: image/gif
connection: close
last-modified: Wed, 22 Oct 2025 07:18:46 GMT
date: Thu, 14 May 2026 03:50:12 GMT
server: OpenBSD httpd
content-length: 56669
redirects: 0
uri: https://www.openbsd.org/images/puffy78.gif
, data: Instance of 'ResponseBody'}
$ ls -lhart puffy78.gif
-rw-rw-r-- 1 user user 56K 14 mai 05:50 puffy78.gif
It seems the function correctly downloaded the file, and when opening it with feh, the gif looks good.
What about OPTIONS, TRACE and/or custom HTTP Methods?
At this time, OPTIONS and TRACE HTTP methods have not been implemented in dio, but it could be possible to craft them using the request() method and setting the method attribute from the Options class to the correct String.
OPTIONS method can be useful when using CORS and then, can be sometime used by developers or administrator to check if everything is right.
The OPTIONS HTTP method requests permitted communication options for a given URL or server. This can be used to test the allowed HTTP methods for a request, or to determine whether a request would succeed when making a CORS preflighted request.
This time, the request() method can be used. This one permits to modify the Options object containing the method attribute, defined as String.
Unfortunately, the echo service does not support OPTIONS nor TRACE HTTP method, we will need to use nc -kl 8080 for this test.
Future<int> customRequest(String method) async {
dio.options.method = method;
var response = await dio.request('http://localhost:8080/');
printResponse(response);
return 0;
}
We are not really interested of the response here, but more about what the client will send to the server. From the netcat side, here the raw output:
$ nc -kl 8080
OPTIONS / HTTP/1.1
user-agent: dio
connection: close
x-custom: custom
accept-encoding: gzip
content-length: 0
host: localhost:8080
The client is correctly using the method we have manually set. The same procedure can also be applied for the TRACE method.
What about HTTP/2?
HTTP/2 is more a recent protocol upgrading HTTP/1.1 by adding multiplexed connection and use binary format (instead of text) for headers.
HTTP/2 provides an optimized transport for HTTP semantics. HTTP/2 supports all of the core features of HTTP/1.1 but aims to be more efficient in several ways. The basic protocol unit in HTTP/2 is a frame (Section 4.1). Each frame type serves a different purpose. [...] Multiplexing of requests is achieved by having each HTTP request/response exchange associated with its own stream. [...] Flow control and prioritization ensure that it is possible to efficiently use multiplexed streams. [...] HTTP/2 adds a new interaction mode whereby a server can push responses to a client. [...] Because HTTP header fields used in a connection can contain large amounts of redundant data, frames that contain them are compressed.
What kind of advantage to use HTTP/2 instead of HTTP/1.1? Better performance, especially when it comes to send multiple requests in parallel. Unfortunately, it does not fix everything, like slow connections issues, or a way for the server to easily close a connection, those issues are still relevant with HTTP/2, mainly due to the network layer above it: TCP.
Most - if not all - modern HTTP servers are supporting it (nginx, apache2, cowboy...) , the same happens for modern clients. dio needs the http2_adapter plugin to support this protocol though.
dart pub add dio_http2_adapter
dart pub get
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
Future<int> http2Request() async {
dio.httpClientAdapter = Http2Adapter(
ConnectionManager(idleTimeout: const Duration(seconds: 10)),
);
var response = await dio.get('/');
printResponse(response);
return 0;
}
$ dart run bin/httpcat.dart http2
{status: 200, status_msg: null, uri: https://echo.free.beeceptor.com/, headers: access-control-allow-origin: *
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Thu, 14 May 2026 07:52:12 GMT
vary: Accept-Encoding
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:34174, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Via: 2.0 Caddy, X-Custom: custom, Accept-Encoding: gzip}, parsedQueryParams: {}}}
I'm not really sure if it correctly using HTTP/2 protocol while using the echo service. Let add a logging feature to see if we can know a bit more there. To do that, we can add a new LogInterceptor object in the current interceptor queue.
Future<int> http2Request() async {
dio.httpClientAdapter = Http2Adapter(
ConnectionManager(idleTimeout: const Duration(seconds: 10)),
);
dio.interceptors.add(
LogInterceptor(
logPrint: (o) => print(o.toString()),
request: true,
requestBody: true,
requestHeader: true,
requestUrl: true,
responseBody: true,
responseHeader: true,
responseUrl: true,
)
);
var response = await dio.get('/');
printResponse(response);
return 0;
}
Execute the code a second time with the logs now:
$ dart run bin/httpcat.dart http2
*** Request ***
uri: https://echo.free.beeceptor.com/
method: GET
responseType: ResponseType.json
followRedirects: true
persistentConnection: false
connectTimeout: 0:00:05.000000
sendTimeout: null
receiveTimeout: 0:00:05.000000
receiveDataWhenStatusError: true
extra: {}
headers:
user-agent: dio
x-custom: custom
data:
null
*** Response ***
uri: https://echo.free.beeceptor.com/
statusCode: 200
headers:
access-control-allow-origin: *
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Thu, 14 May 2026 08:00:36 GMT
vary: Accept-Encoding
via: 1.1 Caddy
Response Text:
{"method":"GET","protocol":"https","host":"echo.free.beeceptor.com","path":"/","ip":"1.2.3.4:50442","headers":{"Host":"echo.free.beeceptor.com","User-Agent":"dio","Via":"2.0 Caddy","X-Custom":"custom","Accept-Encoding":"gzip"},"parsedQueryParams":{}}
{status: 200, status_msg: null, uri: https://echo.free.beeceptor.com/, headers: access-control-allow-origin: *
alt-svc: h3=":443"; ma=2592000
content-type: application/json
date: Thu, 14 May 2026 08:00:36 GMT
vary: Accept-Encoding
via: 1.1 Caddy
, data: {method: GET, protocol: https, host: echo.free.beeceptor.com, path: /, ip: 1.2.3.4:50442, headers: {Host: echo.free.beeceptor.com, User-Agent: dio, Via: 2.0 Caddy, X-Custom: custom, Accept-Encoding: gzip}, parsedQueryParams: {}}}
Yeah, still not really sure if we are using HTTP/2 there. Let use netcat instead, just to be certain.
$ nc -kl | hexdump -C
00000000 50 52 49 20 2a 20 48 54 54 50 2f 32 2e 30 0d 0a |PRI * HTTP/2.0..|
00000010 0d 0a 53 4d 0d 0a 0d 0a 00 00 06 04 00 00 00 00 |..SM............|
00000020 00 00 02 00 00 00 00 00 00 5b 01 04 00 00 00 01 |.........[......|
00000030 00 07 3a 6d 65 74 68 6f 64 03 47 45 54 00 05 3a |..:method.GET..:|
00000040 70 61 74 68 01 2f 00 07 3a 73 63 68 65 6d 65 04 |path./..:scheme.|
00000050 68 74 74 70 00 0a 3a 61 75 74 68 6f 72 69 74 79 |http..:authority|
00000060 09 6c 6f 63 61 6c 68 6f 73 74 00 0a 75 73 65 72 |.localhost..user|
00000070 2d 61 67 65 6e 74 03 64 69 6f 00 08 78 2d 63 75 |-agent.dio..x-cu|
00000080 73 74 6f 6d 06 63 75 73 74 6f 6d 00 00 00 00 01 |stom.custom.....|
00000090 00 00 00 01 00 00 04 03 00 00 00 00 01 00 00 00 |................|
As you can see, HTTP/2.0 is printed on the netcat side and a part of the data received are in binary format, not text like in HTTP/1.1 version. So, it looks good!
Other examples can be found in dio_http2_adapter README.md and in the example/ directory.
What about HTTP/3?
HTTP/3 provides a transport for HTTP semantics using the QUIC transport protocol and an internal framing layer similar to HTTP/2. Once a client knows that an HTTP/3 server exists at a certain endpoint, it opens a QUIC connection. QUIC provides protocol negotiation, stream-based multiplexing, and flow control. Within each stream, the basic unit of HTTP/3 communication is a frame. Each frame type serves a different purpose. Multiplexing of requests is performed using the QUIC stream abstraction. Each request-response pair consumes a single QUIC stream. Streams are independent of each other, so one stream that is blocked or suffers packet loss does not prevent progress on other streams. [...] As in HTTP/2, request and response fields are compressed for transmission.
Unfortunately, it seems QUIC and HTTP/3 are not currently supported everywhere, it will depend of the platform used. The package pure_dart_quic seems to be the solution here. I don't think the solution was currently very stable, and in fact, the addition of this protocol in nginx for example is from the 1.25.0 version (released May 2023). The cowboy server is also offering its implementation but not by default since the beginning of 2025 (version 2.13.0). So, it will be the main subject for another article, outside of dio (or perhaps to extend it).
Want More About dio?
If you are here, that means you enjoyed these notes... And you probably want to know even more about dio. Perhaps having a small glimpse of its usage in real world? You can easily find few open source projects using it:
Diohub: open-source unofficial GitHub mobile client
Flutter Crypto APP: Complete Flutter Application with Riverpod & Freezed + Dio for API REST.
Vexana: Vexana is easy to use network process with dio.
randomuser: Example project to present Dio, RxDart and Bloc
Flutter User Management App with Multi-Source Backend Support: Flutter-based user management application that demonstrates full CRUD (Create, Read, Update, Delete) operations. The app interacts with a RESTful API to manage users and their associated data, including posts, todos, and comments.
youtube_video: a Flutter application built to demonstrate the use of Modern development tools with best practices implementation like Clean Architecture, Modularization, Dependency Injection, BLoC, etc.
And to end this section, a list of interesting dio plugins:
dio_file_uploader: A en_file_uploader plugin to handle the file upload using dio package.datadome_flutter_dio: provides an interceptor that filters and validates all requests to ensure your app networking layer is protected with DataDome.dio_builder: a builder pattern for configuring and creating instances of the Dio HTTP client.embrace_dio: enable the embrace plugin to capture network requests made with the Dio package.alice_dio: Alice is an HTTP Inspector tool for Flutter which helps debugging http requests.dio_http_logger: A powerful network interceptor for Dio, providing comprehensive logging of requests, responses and errors.dio_retry_plus: A plugin for dio that retries failed requests.chunked_uploader: A plugin to upload files to server in chunks.oauth_dio: A customizable oauth client with token storage and interceptors for dio.dio_ansi_logger: A beautiful, Postman-style Dio interceptor that logs HTTP requests and responses with ANSI colors, structured formatting, and fully customizable themes.dio_cache: A plugin for dio that caches responses in a database for better optimization and offline data access.pl_api_helper: pl_api_helper is a Flutter plugin that simplifies API calls, caching, and data management with support for both Dio and HTTP clients.
References and Resources
Dio vs HTTP in Flutter: A practical, Clear Comparison by @heyroziq
Flutter dart API REST with Dio by @stephane_couget_54f540812
Efficient CRUD Operations in Flutter: A Guide to Implementing HTTP Requests with Clean Architecture and Dio by @nikki_eke
Cover Image by Ilona Ivanova on Unsplash

Top comments (0)