Basic and advanced networking in Dart and Flutter β the Tide way. Part 3: HTTP client and request interceptors with dio. Basic.
Feeling lost? Check out the introduction into this series.
Parts 3 and 4 of this series are dedicated to setting up an HTTP client used to load data from the backend.
This part aims to explore the dio object and show the basic setup. By the end of this part, the solution is ready for the first API request.
dio is a powerful HTTP client for Dart with the support of numerous critical and bonus features. For motivation, installation instructions, and basic implementation details refer to the docs.
In this part:
1. dio HTTP client
2. dio interceptors
3. first API request
For advanced topics check out Part 4. It talks about:
1. mocked API
2. proxy
3. SSL pinning
0. Prerequisites
When this series is released, the latest Flutter version is 3.0.
The latest versions of required dependencies in pubspec.yaml
file are:
Further code examples are built on top of the code developed in Part 2, which can be found under the part-2 tag in the Flutter Advanced Networking GitHub repository.
1. dio HTTP client
dio
is an object used to obtain data from the API. Each request can be configured individually, and dio
also offers a BaseOptions
class for common setup for all requests:
With this setup, each request will have an additional query parameter: ?parameter1=value1
and a header {"header1":"value1"}
.
dio
object exposes get
, post
, put
, patch
, delete
, and other methods, which all are wrappers over a universal request
method:
In this example:
the resulting GET request will be sent to https://example.com/api/get_example URL and have two query parameters: ?parameter1=value1¶meter2=value2
, and two headers: {"header1":"value1", "header2":"value2"}
. One header and one query parameter would come from the common dio
setup, and another pair of header and query parameter would come from the setup in the getExample
method.
Unlike get
, other methods also accept dynamic data
parameter to be sent in the request:
In this example:
the resulting POST request will be sent to https://example.com/api/post_example URL and have only query parameter and header from the common dio
setup, and also a {"id": "example_id"}
body.
Refer to the docs for more dio
settings and methods. Moving on to the dio features we are using in Tide Flutter projects.
2. dio interceptors
A dio
interceptor is an object with onRequest
, onResponse
, onError
callbacks that are triggered when a respective event happens with dio
object. Multiple interceptors can be attached to the same dio
object.
Letβs say there is a requirement to send a timestamp with each request. One way would be to explicitly do so, as on line 11
:
As you can imagine, this logic will quickly be duplicated all over the project. And itβs not that hard to forget about such a requirement here and there. Instead, this is a perfect case for an interceptor:
When attached to dio
object, its onRequest
method will append the ts
query parameter to every request:
In fact, the Marvel Comic API imposes a more sophisticated requirement. Each request has to have three query parameters:
- apikey β your public key;
- ts β a timestamp (or other long string which can change on a request-by-request basis);
- hash β an md5 digest of the ts parameter, your private key, and your public key, e.g.
md5(ts+privateKey+publicKey)
.
Create an account on https://developer.marvel.com/ and find your pair of the public and private keys under the βMy Developer Accountβ tab.
Again, such a requirement can be easily satisfied with dio
interceptor. To make the implementation a bit more interesting and to cover some real-life scenarios, the MarvelApiAuthInterceptor
will accept the public key as a parameter, and the private key will be asynchronously obtained from an injected service:
After MarvelApiAuthInterceptor
is attached to dio
instance, all requests it makes to Marvel Comic API are authenticated:
Creating an array of interceptors is intentional here. There are several interceptors we typically attach to the dio
instance in Tide projects.
3. First API request
By now we have a dio
instance configured with the proper baseUrl
and the interceptor, responsible for making authorized API requests. In addition, in previous parts, we created the MarvelComic
model and the generic MarvelApiResponse
class that allows parsing JSON responses from the Marvel Comic API.
It enables making the first API request!
The authenticated GET request to https://gateway.marvel.com:443/v1/public/comics URL returns a list of comics wrapped in a generic metadata response:
The MarvelComicsApi
class accepts a configured dio
instance as a constructor parameter on line 7
. In its getComics
method, it performs a get
request to /comics
path on line 12
. The obtained json
data of type Map<String, dynamic>
is passed to MarvelApiResponse.fromJson
constructor on line 15
. The data
field type of the generic MarvelApiResponse
is MarvelPaginatedList
, so a MarvelPaginatedList.fromJson
constructor is called on line 16
. The MarvelPaginatedList
is also a generic class of type MarvelComic
, so MarvelComic.fromJson
is called on line 18
. As a result, getComics
method returns a parsed MarvelApiResponse
object with a list of MarvelComic
models.
Conclusion
We discovered what is dio
and how to leverage its interceptors mechanism, and also made a first authorized request to the Marvel Comic API.
The final version of the code developed in this part is located under the part-3 tag in the Flutter Advanced Networking GitHub repository.
Read on Part 4: HTTP client and request interceptors with dio. Advanced.