flutter_api_request_guard 1.0.1
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.