Flutter + Dio Framework + Best practices
This article will describe best practices for REST communication in Flutter using the Dio http package. The Dreamwod app used the standard http package but we recently migrated to the Dio http package, this article will describe why and some best practices around the architecture.
Background
- The Dreamwod app is talking with a backend Rest API.
- The API is secured with JWT Tokens. One refresh token, long TTL, is exchanged to access tokens with short TTL.
Why Dio?
The primary reasons why we migrated from the standard http package to Dio are:
- It’s easier to add interceptors when handling requests and errors (i.e when refreshing JWT tokens).
- Less boilerplate code.
- Cleaner code.
Architecture
Our flutter app is structured in the following way:
- lib/models: RPC and models with the standard JSON-serializable annotations.
- lib/services: We use one service for every part of the API, for example, user_service.dart, about_service.dart, notification_service.dart etc.
- lib/api: Services are using an API class for communicating with the backend. We have two Dio clients in the class, one is used for all requests except when the token is refreshed, then we use a dedicated Dio client.
Handling refresh of tokens
We have a 15 minutes time-to-live on our access tokens and we refresh them if they have less than 1 minute remaining, see above. We use requestlock.lock() and requestlock.unlock() so we don’t have concurrent refresh requests.
The package jwt_decode can be used to parse the expiration time from a JWTtoken.
Error handling
Using exceptions makes it easier to catch and handle different kinds of errors. Our error interceptor below which handles different HTTP status codes and converts them to appropriate exceptions.
Example of a “catch all” try-catch block.
try {
var user = await UserService().getUser();
yield UserLoaded(user: user);
} on Exception catch (e) {
yield LoadUserFailed(error: e.toString());
}
Example of a when different exceptions are caught.
try {
final response =
await UserService().login(event.email, event.password);
//... other authentication code yield SignInSuccess();
} on UnauthorizedException {
yield SignInFailure('Invalid email or password. Please try again.');
} catch (error) {
yield SignInFailure(error.toString());
}
}
Summary
That's it! Hope it helps!