Package selection guidance compiled from public package information, community data, and project usage.
A Flutter/Dart package that provides a clean abstraction for interacting with REST APIs. It simplifies HTTP requests while handling common challenges like error management and JWT authentication with automatic refresh token support. Designed to streamline API communication across multiple platforms, it reduces boilerplate code and enhances reliability in network operations. Ideal for apps requiring secure, consistent backend integration.
Use cases
Integrating with authenticated REST APIs
Handling HTTP errors gracefully
Automating JWT token refresh
Building scalable backend integrations
Managing API request lifecycle
Supporting cross-platform network calls
Key features
Built-in JWT and refresh token handling
Comprehensive exception handling
Cross-platform compatibility
Simple, reusable API client interface
Automatic request interception
Configurable base URL and headers
Suitable for
Flutter apps with REST backend
Projects requiring secure API access
Developers seeking reduced networking boilerplate
Teams building multi-platform applications
Apps using JWT-based authentication
Considerations
Requires proper JWT token setup
Depends on external http package
May need custom error mapping
Limited to REST-style endpoints
Not suitable for non-HTTP protocols
Authentication flow must be aligned with server logic
Original README
Original README snapshot from the linked source repository. Visit the repository for the latest content.
Rest API Client
A production-ready HTTP client for Flutter applications that simplifies REST API communication with built-in support for JWT authentication, automatic token refresh, response caching, retry logic, and comprehensive error handling.
Features
JWT Authentication - Automatic token management with secure storage (Keychain/EncryptedSharedPreferences)
Token Refresh - Two strategies: response-and-retry or preemptive refresh before expiry
Response Caching - Intelligent caching with configurable expiration
Retry Logic - Exponential backoff for failed requests
// GET requestfinal result = await client.get('/users');
// GET with query parametersfinal result = await client.get('/users', queryParameters: {'page': 1, 'limit': 10});
// POST requestfinal result = await client.post('/users', data: {'name': 'John', 'email': 'john@example.com'});
// PUT requestfinal result = await client.put('/users/1', data: {'name': 'John Updated'});
// PATCH requestfinal result = await client.patch('/users/1', data: {'name': 'John Patched'});
// DELETE requestfinal result = await client.delete('/users/1');
// HEAD requestfinal result = await client.head('/users/1');
With Type Conversion
Use onSuccess to parse responses into typed objects:
final result = await client.get<User>(
'/users/1',
onSuccess: (data) => User.fromJson(data),
);
if (result.hasData) {
User user = result.data!;
print(user.name);
}
Per-Request Options
final result = await client.get(
'/users',
options: RestApiClientRequestOptions(
headers: {'X-Custom': 'value'},
contentType: 'application/json',
requiresAuth: false, // Skip authentication for this request
silentException: true, // Don't broadcast exceptions
),
);
Caching Strategies
Cache-First (Stale-While-Revalidate)
Returns cached data immediately, then fetches fresh data:
awaitfor (final result in client.getStreamed('/users')) {
if (result.hasData) {
// First emission: cached data (if available)// Second emission: fresh network dataupdateUI(result.data);
}
}
Cache-Only
Get data from cache only (no network request):
final result = await client.getCached('/users');
if (result.hasData) {
print('Cached data: ${result.data}');
}
Cache-Or-Network
Try cache first, fall back to network:
final result = await client.getCachedOrNetwork('/users');
Custom Cache Duration
final result = await client.get(
'/users',
cacheLifetimeDuration: Duration(hours: 1),
);
Authentication
Login Flow
// 1. Make login requestfinal loginResult = await client.post('/auth/login', data: {
'email': 'user@example.com',
'password': 'password123',
});
// 2. Authorize the client with received tokensif (loginResult.hasData) {
await client.authorize(
jwt: loginResult.data['accessToken'],
refreshToken: loginResult.data['refreshToken'],
);
}
// 3. All subsequent requests will include the JWTfinal userResult = await client.get('/users/me');
Check Authorization Status
final isLoggedIn = await client.isAuthorized();
Logout
await client.unAuthorize();
await client.clearStorage(); // Clear tokens and cached data
Error Handling
Result Object
All requests return a Result<T> object:
final result = await client.get('/users');
// Check for dataif (result.hasData) {
print(result.data);
}
// Check for errorsif (result.isError) {
print(result.exception);
}
// Check specific error typesif (result.isConnectionError) {
print('No internet connection');
}
// Access HTTP statusprint(result.statusCode); // e.g., 200, 404, 500print(result.statusMessage); // e.g., "OK", "Not Found"
Exception Types
Exception
HTTP Status
Description
ValidationException
400, 404, 422
Input validation errors
UnauthorizedException
401
Authentication required
ForbiddenException
403
Access denied
ServerErrorException
500, 502
Server-side errors
NetworkErrorException
-
Connection issues
Global Exception Stream
Listen to all exceptions across the app:
client.exceptionHandler.exceptions.stream.listen((exception) {
if (exception isUnauthorizedException) {
// Redirect to login
navigatorKey.currentState?.pushReplacementNamed('/login');
} elseif (exception isNetworkErrorException) {
// Show offline bannershowSnackBar('No internet connection');
}
});
Suppress Exceptions
// Suppress for a single requestfinal result = await client.get(
'/users',
options: RestApiClientRequestOptions(silentException: true),
);
File Downloads
final result = await client.download(
'/files/document.pdf',
'/path/to/save/document.pdf',
onReceiveProgress: (received, total) {
final progress = (received / total * 100).toStringAsFixed(0);
print('Download progress: $progress%');
},
);
Headers
// Set content type
client.setContentType('application/json');
// Set language
client.setAcceptLanguageHeader('en');
// Add custom header
client.addOrUpdateHeader(key: 'X-Api-Key', value: 'your-api-key');
// Access current headersprint(client.headers);