Build a Production Ready Flutter API Client with Automatic Token Refresh
Most Flutter applications eventually need a robust networking layer.
Handling authentication, token refresh, retry mechanisms, file uploads, and error handling often leads to a lot of boilerplate code.
To simplify this problem, I built an open-source package called advanced_api_client — a production-ready API client for Flutter built on top of Dio.
Suddenly your codebase starts filling with:
• Token refresh logic
• Retry mechanisms
• Authentication headers
• Error handling everywhere
• File upload logic
• Session expiration handling
• Duplicate request prevention
What started as a simple API client quickly turns into hundreds of lines of boilerplate code.
In this article, we’ll look at how to build a production-ready networking layer in Flutter and how a reusable API client can simplify this entire process.
Installing the Package
Add the dependency to your pubspec.yaml:
dependencies:
advanced_api_client: ^1.0.2
Then run:
flutter pub get
The Problem with Raw API Integration
A typical Flutter API call using Dio might look like this:
final dio = Dio();
final response = await dio.get(
"https://api.example.com/users",
options: Options(
headers: {
"Authorization": "Bearer $token"
},
),
);
This works fine for simple apps. But real production apps require much more:
• Automatic token refresh when the server returns 401
• Retrying requests when network errors occur
• Centralized error handling
• Managing file uploads
• Handling session expiration
• Preventing duplicate API calls
• Rate limiting requests
Without proper architecture, all this logic gets scattered across the entire project.
This makes the codebase difficult to maintain and scale.
The Production Networking Requirements
A robust API layer for a production Flutter application typically needs:
Authentication Handling
Automatically attach tokens to every request.Token Refresh Flow
When a request returns401, refresh the token and retry the request.Retry Mechanism
Retry failed requests caused by network issues or timeouts.File Upload Support
Handle single and multiple file uploads.Global Error Handling
Centralize API error management.Session Expiration Handling
Automatically log the user out if refresh fails.Request Deduplication
Prevent duplicate API calls.Rate Limiting
Avoid sending too many requests within a short time.
Implementing all of this manually is time-consuming and error-prone.
A Better Approach: A Centralized API Client
Instead of repeating networking logic everywhere, we can centralize it in a reusable API client.
This is exactly what advanced_api_client is designed to do.
It is a production-ready Flutter/Dart HTTP client built on top of Dio that provides advanced networking capabilities out of the box.
Key Features
Some of the features include:
• Automatic token attachment
• Automatic token refresh
• Retry interceptor for network failures
• Single and multiple file uploads
• Request deduplication
• Rate limiting
• Global error handling
• Session expiration management
• Custom interceptor support
• Works with Flutter and pure Dart
The goal is to remove repetitive networking boilerplate and provide a scalable architecture for real-world apps.
Initializing the API Client
Before using the client, initialize it in main().
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await AdvancedApiClient.initialize(
config: ApiConfig(
baseUrl: "https://api.example.com",
enableAutoRefresh: true,
onSessionExpired: () {
final navigator = navigatorKey.currentState;
navigator?.pushNamedAndRemoveUntil(
"/login",
(route) => false,
);
},
refreshConfig: RefreshConfig(
path: "/auth/refresh",
method: "POST",
body: {"from_source": 1},
tokenParser: (data) => data["access_token"],
),
),
);
runApp(MyApp());
}
This setup configures:
• Base API URL
• Token refresh endpoint
• Session expiration handling
Once initialized, the client can be accessed anywhere in the app.
Making API Requests
Using the client is simple.
GET Request
final client = AdvancedApiClient.instance;
await client.get(
endpoint: "/users",
);
POST Request
await client.post(
endpoint: "/users",
body: {
"name": "John",
},
);
PUT Request
await client.put(
endpoint: "/users/1",
body: {
"name": "Updated Name",
},
);
File Upload Example
Uploading files is also straightforward.
await client.upload(
endpoint: "/upload",
files: {
"image": [imagePath],
},
);
You can even upload multiple files or different file fields in a single request.
Handling Session Expiration
If the refresh token fails, the client automatically triggers the session expiration callback.
await AdvancedApiClient.instance.terminateSession();
This clears tokens and cancels all pending requests.
Built-in Retry Mechanism
The client automatically retries requests when network issues occur.
Supported scenarios include:
• Connection errors
• Receive timeouts
• Temporary server issues
This significantly improves the reliability of API communication in unstable networks.
Comparison with Raw Dio Usage
| Feature | Raw Dio | Advanced API Client |
|---|---|---|
| Basic requests | ✅ | ✅ |
| Token refresh | Manual | Built-in |
| Retry mechanism | Manual | Built-in |
| File uploads | Basic | Advanced |
| Request deduplication | ❌ | ✅ |
| Rate limiting | ❌ | ✅ |
| Session termination | ❌ | ✅ |
| Architecture ready | ❌ | ✅ |
As applications grow, the benefits of a structured API client become increasingly obvious.
When Should You Use This Approach?
This architecture is especially useful for:
• Enterprise applications
• E-commerce platforms
• SaaS products
• Fintech applications
• Apps with complex authentication flows
Centralizing API logic dramatically improves maintainability and scalability.
Final Thoughts
Networking code often becomes the most chaotic part of a growing Flutter application.
By centralizing API logic into a reusable client, we can:
• Reduce boilerplate code
• Improve reliability
• Simplify authentication flows
• Build scalable architecture
Tools like advanced_api_client help developers focus on building features instead of rewriting the same networking logic repeatedly.
If you found this useful, feel free to explore the package and contribute to the project.
Happy coding! 🚀
Top comments (0)