flutter_api_request_guard 1.0.1 copy "flutter_api_request_guard: ^1.0.1" to clipboard
flutter_api_request_guard: ^1.0.1 copied to clipboard

A lightweight Dart and Flutter package for preventing duplicate API requests, caching successful responses, and retrying failed requests.

flutter_api_guard #

A lightweight Flutter and Dart package for making API requests safer and more efficient by preventing duplicate concurrent requests, caching successful responses, and retrying failed requests.

Why flutter_api_guard? #

In many Flutter applications, the same API can accidentally be called multiple times because of:

  • Multiple button taps
  • Widget rebuilds
  • Multiple screens requesting the same resource
  • Concurrent asynchronous operations
  • Temporary network failures
  • Repeated requests for data that could be cached

For example, a user may tap a button several times:

User taps button
      ↓
API request #1
User taps again
      ↓
API request #2
User taps again
      ↓
API request #3

This can result in unnecessary network traffic, duplicated server work, slower applications, and unexpected behavior.

flutter_api_guard provides a simple way to control these requests:

Multiple callers
      ↓
┌─────────────────────┐
│   ApiGuard          │
│                     │
│ Duplicate protection│
│ Cache               │
│ Retry               │
└──────────┬──────────┘
           ↓
      Actual API
        Request

Features #

  • Prevent duplicate concurrent API requests
  • Share the result of an active request with multiple callers
  • In-memory response caching
  • Configurable cache expiration
  • Automatic retry for failed requests
  • Configurable retry delay
  • Clear the complete cache
  • Remove a specific cached request
  • Generic response types
  • No dependency on a specific HTTP client
  • Works with http, Dio, or your own API client
  • Pure Dart implementation
  • Designed for Flutter and Dart applications

Installation #

Add flutter_api_guard to your project:

flutter pub add flutter_api_guard

Or add it manually to pubspec.yaml:

dependencies:
  flutter_api_guard: ^1.0.0

Then run:

flutter pub get

Import #

import 'package:flutter_api_guard/flutter_api_guard.dart';

Quick Start #

Create an ApiGuard instance:

final apiGuard = ApiGuard();

Then wrap your API request:

final result = await apiGuard.request<String>(
  key: 'users',
  request: () async {
    return await getUsers();
  },
);

The key uniquely identifies the request.

If multiple callers request the same key while the request is still running, ApiGuard prevents duplicate network calls.


Basic Usage #

Suppose you have an API method:

Future<String> getUsers() async {
  // Your real API call
  return 'Users loaded';
}

Use it with ApiGuard:

final apiGuard = ApiGuard();

final users = await apiGuard.request<String>(
  key: 'users',
  request: getUsers,
);

print(users);

Prevent Duplicate API Requests #

This is one of the main features of flutter_api_guard.

Consider:

final apiGuard = ApiGuard();

final result1 = apiGuard.request<String>(
  key: 'users',
  request: getUsers,
);

final result2 = apiGuard.request<String>(
  key: 'users',
  request: getUsers,
);

final result3 = apiGuard.request<String>(
  key: 'users',
  request: getUsers,
);

final results = await Future.wait([
  result1,
  result2,
  result3,
]);

Even though three callers request the same resource, only one actual request is executed while the request is active.

Conceptually:

Caller 1 ─────┐
Caller 2 ─────┼──→ ApiGuard ──→ One API request
Caller 3 ─────┘                     ↓
                                  Result
                                    ↓
                         ┌──────────┼──────────┐
                         ↓          ↓          ↓
                      Caller 1   Caller 2   Caller 3

This is useful for preventing duplicate requests caused by:

  • Multiple button taps
  • Multiple widgets
  • Multiple controllers
  • Concurrent operations

Request Keys #

The key identifies a request.

For example:

key: 'users'

For different resources, use different keys:

key: 'users'
key: 'products'
key: 'profile'
key: 'notifications'

For requests containing parameters, include the parameters in the key.

For example:

final userId = 123;

final result = await apiGuard.request<String>(
  key: 'user_$userId',
  request: () => getUser(userId),
);

This allows different users to have independent request identities:

user_101
user_102
user_103

Caching #

You can cache a successful API response by providing cacheDuration.

final users = await apiGuard.request<List<User>>(
  key: 'users',
  request: getUsers,
  cacheDuration: const Duration(minutes: 5),
);

After the first successful request, subsequent requests with the same key can return the cached value until the cache expires.

Example:

First request
     ↓
API
     ↓
Response
     ↓
Cache for 5 minutes

Second request
     ↓
Cache
     ↓
Response

Third request
     ↓
Cache
     ↓
Response

This can reduce unnecessary API calls and improve application responsiveness.


Cache Expiration #

The cache duration is configurable:

cacheDuration: const Duration(seconds: 30),

or:

cacheDuration: const Duration(minutes: 5),

or:

cacheDuration: const Duration(hours: 1),

Example:

final data = await apiGuard.request<String>(
  key: 'home',
  request: getHomeData,
  cacheDuration: const Duration(minutes: 10),
);

Once the cache expires, the next request will execute the API call again.


Retry Failed Requests #

You can automatically retry a failed request.

final result = await apiGuard.request<String>(
  key: 'users',
  request: getUsers,
  retry: 3,
);

If the API fails, ApiGuard retries according to the configured retry count.

For example:

Attempt 1
   ↓
Failed
   ↓
Attempt 2
   ↓
Failed
   ↓
Attempt 3
   ↓
Success

If all attempts fail, the final error is propagated to the caller.


Configure Retry Delay #

You can control how long the guard waits between attempts:

final result = await apiGuard.request<String>(
  key: 'users',
  request: getUsers,
  retry: 3,
  retryDelay: const Duration(seconds: 2),
);

The request will wait two seconds between retry attempts.


Combining Cache + Retry + Duplicate Protection #

You can combine all supported features:

final users = await apiGuard.request<List<User>>(
  key: 'users',
  request: getUsers,
  cacheDuration: const Duration(minutes: 5),
  retry: 3,
  retryDelay: const Duration(seconds: 1),
);

This gives you:

                API REQUEST
                     │
                     ▼
             Duplicate check
                     │
             ┌───────┴───────┐
             │               │
          Cached?          Not cached
             │               │
             ▼               ▼
          Return          API call
                            │
                       ┌────┴────┐
                       │         │
                    Success     Failure
                       │         │
                       ▼         ▼
                     Cache     Retry
                       │         │
                       ▼         ▼
                    Return    Success/Error

Cache Management #

Clear All Cache #

Remove every cached entry:

apiGuard.clearCache();

Use this when you want to invalidate all cached API responses.

For example, after logout:

await logout();

apiGuard.clearCache();

Remove a Specific Cache Entry #

Remove only one cached request:

apiGuard.removeCache('users');

For example:

apiGuard.removeCache('profile');

This allows the next request for that key to fetch fresh data.


Check Whether a Request Is Running #

You can check whether a request is currently active:

final isRunning = apiGuard.isRequestRunning('users');

print(isRunning);

This returns:

true

while the request is active and:

false

after the request has completed.


Using with package:http #

flutter_api_guard does not require a specific HTTP client.

For example:

import 'dart:convert';

import 'package:http/http.dart' as http;
import 'package:flutter_api_guard/flutter_api_guard.dart';

final apiGuard = ApiGuard();

Future<List<dynamic>> getUsers() async {
  final response = await http.get(
    Uri.parse('https://example.com/users'),
  );

  if (response.statusCode != 200) {
    throw Exception('Failed to load users');
  }

  return jsonDecode(response.body) as List<dynamic>;
}

Then:

final users = await apiGuard.request<List<dynamic>>(
  key: 'users',
  request: getUsers,
  cacheDuration: const Duration(minutes: 5),
  retry: 2,
);

Using with Dio #

ApiGuard can also wrap Dio requests.

import 'package:dio/dio.dart';
import 'package:flutter_api_guard/flutter_api_guard.dart';

final dio = Dio();
final apiGuard = ApiGuard();

Future<dynamic> getUsers() async {
  final response = await dio.get(
    'https://example.com/users',
  );

  return response.data;
}

Then:

final users = await apiGuard.request<dynamic>(
  key: 'users',
  request: getUsers,
  cacheDuration: const Duration(minutes: 5),
  retry: 2,
);

The package does not control your HTTP client. It simply wraps the asynchronous operation.


Using with Flutter State Management #

ApiGuard can be used with different state-management approaches because it does not depend on a specific state-management package.

It can be used with:

  • Provider
  • Riverpod
  • Bloc
  • Cubit
  • GetX
  • MobX
  • ValueNotifier
  • StatefulWidget
  • ChangeNotifier
  • Custom architectures

Example:

class UserRepository {
  final ApiGuard apiGuard;

  UserRepository(this.apiGuard);

  Future<List<User>> getUsers() {
    return apiGuard.request<List<User>>(
      key: 'users',
      request: fetchUsers,
      cacheDuration: const Duration(minutes: 5),
      retry: 2,
    );
  }
}

This keeps request protection inside the repository layer instead of putting networking logic inside your UI.


Recommended Architecture #

For larger Flutter applications, we recommend keeping ApiGuard below the UI layer:

Flutter UI
    │
    ▼
Controller / ViewModel
    │
    ▼
Repository
    │
    ▼
ApiGuard
    │
    ▼
HTTP Client
    │
    ▼
Backend API

Example:

class UserRepository {
  UserRepository(this.apiGuard);

  final ApiGuard apiGuard;

  Future<List<User>> getUsers() {
    return apiGuard.request<List<User>>(
      key: 'users',
      request: fetchUsers,
      cacheDuration: const Duration(minutes: 5),
      retry: 2,
    );
  }

  Future<List<User>> fetchUsers() async {
    // Your HTTP implementation
    return [];
  }
}

This approach keeps your application responsibilities separated.


Important: Choosing Good Cache Keys #

Cache keys should uniquely represent the data being requested.

Good:

'users'
'profile_123'
'products_page_1'
'products_page_2'
'search_flutter'

Avoid using the same key for unrelated requests:

'api'

for every endpoint.

A collision can cause unrelated requests to share the same cache/request identity.


Error Handling #

ApiGuard does not hide the final request error.

You can handle errors normally:

try {
  final users = await apiGuard.request<List<User>>(
    key: 'users',
    request: getUsers,
    retry: 3,
  );

  print(users);
} catch (error) {
  print('API failed: $error');
}

This allows your application to decide how to display or handle the error.


When Should You Use ApiGuard? #

flutter_api_guard is useful when your application has:

  • Expensive API requests
  • Frequently rebuilt widgets
  • Multiple screens requesting the same data
  • Buttons that can be tapped repeatedly
  • Temporary network failures
  • Frequently requested read-only data
  • Repository-based architectures

When Should You Avoid Caching? #

Caching isn't appropriate for every API.

Be careful when working with highly dynamic data such as:

  • Payment status
  • Real-time balances
  • Live tracking
  • Frequently changing server state
  • Security-sensitive information

Use an appropriate cache duration—or don't cache those responses at all.


API Reference #

ApiGuard #

Creates an API request guard.

ApiGuard()

request<T>() #

Executes a guarded asynchronous request.

Future<T> request<T>({
  required String key,
  required Future<T> Function() request,
  Duration? cacheDuration,
  int retry = 0,
  Duration retryDelay = const Duration(seconds: 1),
})

Parameters:

Parameter Type Required Description
key String Yes Unique identifier for the request
request Future<T> Function() Yes Actual API operation
cacheDuration Duration? No How long successful results are cached
retry int No Number of retry attempts
retryDelay Duration No Delay between retry attempts

clearCache() #

Clears all cached responses.

apiGuard.clearCache();

removeCache() #

Removes a specific cached response.

apiGuard.removeCache('users');

isRequestRunning() #

Checks whether a request is currently active.

apiGuard.isRequestRunning('users');

Supported Platforms #

flutter_api_guard is implemented using Dart/Flutter APIs without platform-specific native code.

It is designed to support:

  • Android
  • iOS
  • Web
  • Windows
  • macOS
  • Linux

Actual application compatibility can also depend on the HTTP client and other dependencies used by your application.


Performance Considerations #

flutter_api_guard uses an in-memory cache.

This means:

  • Cache data is stored in memory.
  • Cache is lost when the application process terminates.
  • It is not intended to replace persistent storage.
  • Large responses should not be cached indefinitely.

For persistent data, consider using a dedicated storage solution such as Hive, SQLite, Isar, or another persistence layer.


Threading and Concurrency #

The package is designed around Dart's asynchronous Future model.

Concurrent callers using the same request key can share the same active request rather than creating multiple independent API calls.

This makes it particularly useful for preventing request duplication in asynchronous Flutter applications.


Security #

flutter_api_guard does not provide authentication, encryption, authorization, or secure storage.

Do not use the in-memory cache as a secure storage mechanism for sensitive credentials or secrets.

Authentication and authorization should remain the responsibility of your application's networking/security layer.


Testing #

The package includes automated tests covering the core request-guard behavior.

Run:

flutter test

Static analysis:

dart analyze

Format the project:

dart format .

Validate package publication:

dart pub publish --dry-run

Example Project #

A complete example application is available in the example/ directory.

The example demonstrates how to:

  • Create an ApiGuard
  • Execute API requests
  • Prevent duplicate requests
  • Cache responses
  • Configure retries
  • Manage cached data

Roadmap #

Planned improvements may include:

  • Exponential backoff
  • Jitter for retry delays
  • Maximum retry duration
  • Cache statistics
  • Request cancellation
  • Cache size limits
  • Persistent cache adapters
  • Request timeout support
  • Request lifecycle callbacks
  • Logging/debug mode
  • Custom cache implementations

Roadmap items may change based on community feedback.


Contributing #

Contributions are welcome.

Before submitting a pull request:

dart format .
dart analyze
flutter test
dart pub publish --dry-run

Please open an issue before making large architectural changes so the proposed change can be discussed first.


Issues and Feature Requests #

If you find a bug or have an idea for improving the package, please open an issue in the GitHub repository.

When reporting a bug, include:

  • Flutter version
  • Dart version
  • Package version
  • Platform
  • Minimal reproduction
  • Expected behavior
  • Actual behavior

This helps us investigate issues more quickly.


License #

This package is released under the MIT License.

See the LICENSE file for details.


Author #

Created and maintained by Ramesh Kushwaha.

If you find flutter_api_guard useful, consider giving the project a ⭐ on GitHub.

0
likes
140
points
106
downloads

Documentation

API reference

Publisher

verified publisherappvora.in

Weekly Downloads

A lightweight Dart and Flutter package for preventing duplicate API requests, caching successful responses, and retrying failed requests.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_api_request_guard