api_client_dio
A simple, opinionated Dio wrapper for Flutter. Handles base URL, token injection, 401 refresh/redirect, logging, and timeouts out of the box.
Framework-agnostic — no GetX, no Riverpod, no dependency other than Dio.
Features
- ✅ Zero boilerplate — Create a client with base URL and start making requests
- ✅ Automatic Bearer token — Implement
TokenProvideronce, tokens are injected automatically - ✅ 401 → refresh → retry — Automatic token refresh when access token expires
- ✅ Auto-redirect to login — Calls
onUnauthorized()when refresh fails - ✅ Request/response logging — Toggleable via
enableLogging: true - ✅ Configurable timeouts — connect, receive, and send timeouts
- ✅ Full Dio support — GET, POST, PUT, PATCH, DELETE with all Dio options
- ✅ Runtime base URL — Change API endpoint on the fly via
updateBaseUrl()
Getting started
dependencies:
api_client_dio: ^0.1.0
flutter pub get
Usage
1. Implement TokenProvider
Connect your token storage (SharedPreferences, flutter_secure_storage, etc.):
import 'package:api_client_dio/api_client_dio.dart';
class StorageTokenProvider implements TokenProvider {
final _storage = FlutterSecureStorage();
@override
Future<String?> get accessToken =>
_storage.read(key: 'access_token');
@override
Future<String?> get refreshToken =>
_storage.read(key: 'refresh_token');
@override
Future<void> onUnauthorized() async {
await _storage.clearAll();
// Navigate to login page
}
@override
Future<void> onTokenRefreshed({
required String accessToken,
String? refreshToken,
}) async {
await _storage.write(key: 'access_token', value: accessToken);
if (refreshToken != null) {
await _storage.write(key: 'refresh_token', value: refreshToken);
}
}
}
2. Create the client
final client = ApiClient(
baseUrl: 'https://api.example.com',
tokenProvider: StorageTokenProvider(),
enableLogging: true, // Print requests/responses
refreshEndpoint: '/auth/refresh', // Enable 401 → refresh → retry
);
3. Use it
// GET
final posts = await client.get('/posts');
final user = await client.get('/users/1');
// POST
final login = await client.post('/auth/login', data: {
'email': 'user@example.com',
'password': 'secret123',
});
// PUT
await client.put('/users/1', data: {'name': 'New Name'});
// DELETE
await client.delete('/posts/1');
// Change base URL at runtime (e.g. after login)
client.updateBaseUrl('https://api.newdomain.com');
API
ApiClient
| Constructor param | Type | Default | Description |
|---|---|---|---|
baseUrl |
String |
(required) | Base URL for all requests |
tokenProvider |
TokenProvider? |
null |
Auth token handler |
connectTimeout |
Duration |
10s |
Connection timeout |
receiveTimeout |
Duration |
30s |
Response timeout |
sendTimeout |
Duration |
10s |
Request send timeout |
enableLogging |
bool |
false |
Print request/response logs |
headers |
Map<String, dynamic>? |
null |
Additional default headers |
refreshEndpoint |
String? |
null |
Token refresh URL path |
refreshDio |
Dio? |
null |
Custom Dio for refresh calls |
interceptors |
List<Interceptor>? |
null |
Additional Dio interceptors |
TokenProvider
| Method | Returns | Description |
|---|---|---|
accessToken |
Future<String?> |
Current Bearer token |
refreshToken |
Future<String?> |
Current refresh token |
onUnauthorized() |
Future<void> |
Called on 401 (clear & redirect) |
onTokenRefreshed(accessToken, refreshToken) |
Future<void> |
Called after successful refresh |
License
MIT
Libraries
- api_client_dio
- A simple, opinionated Dio wrapper for Flutter.