Flutter (79): Http request library - dio

Time: Column:Mobile & Frontend views:214

79.1 Introducing Dio

As mentioned in the previous section, directly using HttpClient to make network requests can be cumbersome, as many aspects require manual handling. When it comes to file uploads/downloads and cookie management, it can become quite complex. Fortunately, the Dart community offers several third-party HTTP request libraries that simplify the process significantly. In this section, we will introduce the popular Dio library.

https://github.com/cfug/dio

Dio is a powerful Dart HTTP request library maintained by the author, supporting RESTful APIs, FormData, interceptors, request cancellation, cookie management, file uploads/downloads, timeouts, and more. The usage of Dio may change with version upgrades, so if the content of this section differs from the latest Dio functionality, please refer to the latest Dio documentation.

79.2 Using Dio to Make Requests

Adding Dio:

dependencies:
  dio: ^x.x.x # Please use the latest version from pub

Importing and Creating a Dio Instance:

import 'package:dio/dio.dart';
Dio dio = Dio();

Now you can use the dio instance to make network requests. Note that a single dio instance can initiate multiple HTTP requests. Generally, when an app has only one HTTP data source, Dio should be used as a singleton.

Making GET Requests:

Response response;
response = await dio.get("/test?id=12&name=wendu");
print(response.data.toString());

For GET requests, you can also pass query parameters as an object. The above code is equivalent to:

response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
print(response);

Making a POST Request:

response = await dio.post("/test", data: {"id": 12, "name": "wendu"});

Making Multiple Concurrent Requests:

response = await Future.wait([dio.post("/info"), dio.get("/token")]);

Downloading a File:

response = await dio.download("https://www.google.com/", _savePath);

Sending FormData:

FormData formData = FormData.from({
  "name": "wendux",
  "age": 25,
});
response = await dio.post("/info", data: formData);

If the data being sent is FormData, Dio will automatically set the request header contentType to multipart/form-data.

Uploading Multiple Files via FormData:

FormData formData = FormData.from({
  "name": "wendux",
  "age": 25,
  "file1": UploadFileInfo(File("./upload.txt"), "upload1.txt"),
  "file2": UploadFileInfo(File("./upload.txt"), "upload2.txt"),
  // Supports uploading an array of files
  "files": [
    UploadFileInfo(File("./example/upload.txt"), "upload.txt"),
    UploadFileInfo(File("./example/upload.txt"), "upload.txt")
  ]
});
response = await dio.post("/info", data: formData);

It’s worth mentioning that Dio internally uses HttpClient to initiate requests, so proxy settings, request authentication, certificate verification, etc., work the same way as with HttpClient. We can configure these in the onHttpClientCreate callback, for example:

(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
  // Set up proxy
  client.findProxy = (uri) {
    return "PROXY 192.168.1.2:8888";
  };
  // Certificate verification
  httpClient.badCertificateCallback = (X509Certificate cert, String host, int port) {
    if (cert.pem == PEM) {
      return true; // If the certificate matches, allow data to be sent
    }
    return false;
  };
};

Note that onHttpClientCreate will be called whenever the current Dio instance needs to create an HttpClient, so configuring the HttpClient through this callback will apply to the entire Dio instance. If the application requires multiple proxies or certificate verification strategies, you can create different Dio instances for each.

Isn't it simple? Besides these basic usages, Dio also supports request configuration, interceptors, etc. The official documentation is quite detailed, so this book will not delve further into those topics. For more information, you can refer to the Dio homepage: Dio GitHub. In the next section, we will implement a chunked downloader using Dio.

79.3 Example

We will use GitHub's open API to request all public open-source projects under the FlutterChina organization, implementing:

  1. A loading indicator during the request phase.

  2. If the request fails, display an error message; if successful, display the project name list.

Here’s the code:

class _FutureBuilderRouteState extends State<FutureBuilderRoute> {
  Dio _dio = Dio();

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      child: FutureBuilder(
        future: _dio.get("https://api.github.com/orgs/flutterchina/repos"),
        builder: (BuildContext context, AsyncSnapshot snapshot) {
          // Request completed
          if (snapshot.connectionState == ConnectionState.done) {
            Response response = snapshot.data;
            // Error occurred
            if (snapshot.hasError) {
              return Text(snapshot.error.toString());
            }
            // Request successful; construct a ListView to display project names
            return ListView(
              children: response.data.map<Widget>((e) =>
                ListTile(title: Text(e["full_name"]))
              ).toList(),
            );
          }
          // Show loading indicator while request is in progress
          return CircularProgressIndicator();
        }
      ),
    );
  }
}