DEV Community

Cover image for Build a Production Ready Flutter API Client with Automatic Token Refresh
Raju ASTR
Raju ASTR

Posted on

Build a Production Ready Flutter API Client with Automatic Token Refresh

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
Enter fullscreen mode Exit fullscreen mode

Then run:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

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"
    },
  ),
);
Enter fullscreen mode Exit fullscreen mode

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:

  1. Authentication Handling
    Automatically attach tokens to every request.

  2. Token Refresh Flow
    When a request returns 401, refresh the token and retry the request.

  3. Retry Mechanism
    Retry failed requests caused by network issues or timeouts.

  4. File Upload Support
    Handle single and multiple file uploads.

  5. Global Error Handling
    Centralize API error management.

  6. Session Expiration Handling
    Automatically log the user out if refresh fails.

  7. Request Deduplication
    Prevent duplicate API calls.

  8. 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());
}
Enter fullscreen mode Exit fullscreen mode

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",
);
Enter fullscreen mode Exit fullscreen mode

POST Request

await client.post(
  endpoint: "/users",
  body: {
    "name": "John",
  },
);
Enter fullscreen mode Exit fullscreen mode

PUT Request

await client.put(
  endpoint: "/users/1",
  body: {
    "name": "Updated Name",
  },
);
Enter fullscreen mode Exit fullscreen mode

File Upload Example

Uploading files is also straightforward.

await client.upload(
  endpoint: "/upload",
  files: {
    "image": [imagePath],
  },
);
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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)