Skip to content

Instantly share code, notes, and snippets.

@Vanethos

Vanethos/blog.md Secret

Created September 5, 2019 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Vanethos/e04a9eba3065007181061d76ab429573 to your computer and use it in GitHub Desktop.
Save Vanethos/e04a9eba3065007181061d76ab429573 to your computer and use it in GitHub Desktop.

Dio Interceptors in Flutter

Sometimes we don’t need complex apps, sometimes we just need an app that displays a list of items from one endpoint, and we can manage to do it with a simple method:

https://gist.github.com/f4e178dbb853f349274ca0773c7ae143

And we don’t have any errors, there’s no need to log our responses. What about cache? Nobody cares about it!

But truth be told, almost no apps are as simple as this one. Some apps do require more in order for us to get the response from our request, such as:

  • Sending dynamic headers to the server, such as a stored key from the shared preferences or the time of the request;
  • Checking each response header and saving its values locally;
  • Verifying the errors sent from the server and directly map them to Error classes that our app understands;
  • Adding a simplified cache to our app so that if the connection is timed out or if the user does not have internet access, we can display the previous response for that request;
  • Additionally, we might want to add logging to all our responses and requests.

Interceptors will help us handle this by giving us specific callbacks for errors, requests and response.

Before diving into how we can use interceptors, let’s take a step back and look at how we can configure Dio.

Dio Configuration

Dio can be configured with a BaseOption object that let us initialise a new Dio instance with a set of rules: connectTimeout, receiveTimeout and baseUrl that will be used for every API call we make.

https://gist.github.com/7ba8c8da63bf16b693ce7bb590fc7f66

However, one thing that we cannot add in the base configurations (at the time the article was written) is interceptors. For that, we need to create the new Dio instance and add the interceptors that we want in the interceptors list.

https://gist.github.com/2679df4ca80e7bc717025a100f5afa5d

And with this, we have setup a Dio instance that can be used for any API call that we make.

Adding dynamic headers

As stated in the introduction, let us suppose that the app that we are making needs a header that contains a value stored in the shared preferences. Additionally, that value must have the timestamp and can change at any time.

/As a side note/: Since the data can be changed, we cannot use the BaseOptions’s extra field, which would conveniently let us access data static data that we passed to it on its creation. So, we will need to access the shared preferences each time we are making a request.

The InterceptorsWrapper gives us the RequestOptions object which has the following properties:

  • Request dynamic data
  • Url String path
  • Query Parameters Map<String, dynamic> queryParameters

With this information, we can start implementing our requestInterceptor method.

This method returns a dynamic type that can be:

  • The RequestOptions object if we want to continue with the request
  • A Response object if we want the app to take care of the request by itself
  • a DioError or dio.reject object, that will throw an error.

This will let us have the flexibility to validate each request before it’s being made, add data, and throw any error if necessary. For our case, we just need to add some data and proceed with the request.

https://gist.github.com/27e60347058c504338c99ddf342c5322

Knock, knock the Project Manager comes to tell you that now, for some reason, you cannot send the headers for a specific set of endpoints.

We could quickly solve this by using a switch statement with the path parameter. But we don’t want that. We want to be able to see in the endpoint declaration if that endpoints needs, or does not need the token. So, instead of searching for the paths in the interceptor, we are going to add an auxiliary header to each endpoint request.

https://gist.github.com/7e388f479a19cb937c792bfccd3cdc0e

Then, we will be able to verify if the request has that header, remove it, and add the token.

https://gist.github.com/008816da48d964adf9678c60459a7688

Verifying the Response

As it was with the Request, we also have a specific key stored in our shared preferences with which we must verify our responses. If we cannot find the specified header or if the key is different than the one stored, we throw an error message saying that the user is no longer active.

In the same way that we created a method for the request, we can do the same for the response. The key difference is that now we are dealing with a Response object which in part has the same data as the Request such as data, headers but it also has the statusCode and the original Request data.

As with the response, we have a dynamic type as a return for this function which can be:

  • The Response object if we want to continue with the request
  • A DioError if we to throw an error after validating the response data

Which means that, if the boolean value for the header isUserActive is false, we can return a DioError object as follows:

https://gist.github.com/a8e756b88262caef440135368b2540ee

Verifying Errors from the Server

Let us assume that we have some mechanism in our server that can delete a user account completely. In this case, the app has to return to the login screen in order for the user to create a new account. The error from the server has the following message: {“error”:”ERROR_001”}, and as with the Response and Request, we will create an interceptor in order to catch all the incoming errors.

Looking at the documentation, we can see that the type for the error interceptor is also dynamic with the following specification:

  • If we want to continue the request with an error, we return the DioError object.
  • If we want to resolve the request and return a Response object, we can do it, in this case, our app is not aware that there has been an error with the server, and continues with the request normally.

To go to a different screen, since we are dealing with the first version of the app (read: something to be refactored later on), we directly navigate the user to another screen from this class using the Navigator widget with a GlobalKey.

https://gist.github.com/0ef0ec70cb774c070f0b828f25a86dec

Creating an Interceptor Class

Creating a function for each interceptor can be an acceptable approach, but what if we have more than one response interceptor? One approach that we can have is to create a class that extends the Interceptor class and overrides the onRequest, onError and onResponse methods.

https://gist.github.com/293a35661225bd9e0327a097ee849759

This class can then be easily added to the Dio object interceptors:

https://gist.github.com/2dd271e5514792557d5777fc645975c2

Putting it all together - Creating a simple Cache

One thing we can do with the interceptors is to create a simple cache for our requests. The concept is simple: if the user is using the app normally and for some reason there’s a network error or a connection-timeout, then we still want the user to see the feed or home page of the app. To do that, we have to save in memory all the requests that we are making, and, when verifying for the connection timeout error or an internal error from Dio (DioErrorType.DEFAULT), if there’s a request that we have saved for the same endpoint, with the same parameters, then we return the old response and warn the user that he’s offline at the moment.

https://gist.github.com/38504610d4dfb709d74caecea24dfae5

We could further improve this cache by modifying our response and give it a parameter to warn the UI that this response is now a cache, or implement a persistent cache with SQL that would let the user see the previous feed if he opens the app offline, much like what the Linkedin app does.

Bonus Tip - Logging Everything Dio

For the majority of the time spent creating a project, we are going to stumble on errors upon errors when making API requests. Maybe we forgot a query parameter, or the header, or the body has a missing parameter or it’s just a case of the backend having a nasty bug. For all these cases, the best would be to have all the requests logged into our console so that we can easily check what has happened.

As with before, we create a new Interceptor class, implement all the necessary methods and log all the information that we want from the requests. Furthermore, since we want our logs to easily stand out, we might want to format them to always start and end with the following:

https://gist.github.com/00ccf76b5e01249ee64f1bb1b699b764

  • For our Request, we want to print out the query parameters and the body of the request (if available) , headers and the URL;
  • For the Response, we want to print out the URL, headers, body and status code
  • As for the Error, we will want the status code and the error itself

https://gist.github.com/e98fc087d6a91da97b515536204b6def

Testing it out in with the Json Placeholder website, we get the following logged:

https://gist.github.com/602dc3a4dbf07c8eec247e6bf5669672

As a further note, we are currently using the print method for displaying the logs. This might not be ideal since these logs will also appear in the production app, which means that anyone who connects the phone with this app open and runs flutter logs will be able to see the full output. As a better alternative, we can use debugPrint with Product Flavors as seen in this article - debugPrint and the power of hiding and customizing your logs in Dart.


And that’s it 👻 with this we have at least mastered the basics of Interceptors in Dio and can now add more functionality, logging and better error handling to our apps.

As always, please do tell me in the comments what are going to be your uses for them!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment