ApiFlow Usage Guide
Overview
ApiFlow provides a functional programming approach to HTTP operations using the Either pattern. Instead of throwing exceptions, it returns explicit success or error results, making error handling more predictable and type-safe.
The Either Pattern
The Either pattern represents a value that can be one of two types:
- Left: Contains error information (
ServiceErrorModel) - Right: Contains success data (
ResponseResultsModel<T>)
This eliminates unexpected exceptions and forces explicit error handling.
Quick Setup
// Initialize once in your app
await EitherApiService.init(
baseUrl: 'https://api.example.com',
enableCaching: true,
boxNameCaching: 'api_cache',
enableLogging: true,
);
Basic Usage Pattern
// Make a request
final result = await EitherApiService.get<User>(
endpoint: '/users/123',
fromJson: (json) => User.fromJson(json),
);
// Handle the result
result.fold(
(error) => print('Error: ${error.message}'),
(success) => print('User: ${success.dataFromApi?.name}'),
);
HTTP Methods
GET Request
Used for retrieving data with automatic caching support.
final result = await EitherApiService.get<List<User>>(
endpoint: '/users',
fromJson: (json) => (json as List).map((e) => User.fromJson(e)).toList(),
queryParameters: {'page': 1, 'limit': 10},
enableCachingRequest: true, // Override global caching setting
);
// Pattern matching
if (result.isRight) {
final users = result.right.dataFromApi;
print('Found ${users?.length} users');
} else {
final error = result.left;
print('Error ${error.statusCode}: ${error.message}');
}
POST Request
Used for creating new resources with automatic list cache invalidation.
final newUser = User(name: 'John', email: 'john@example.com');
final result = await EitherApiService.post<User>(
endpoint: '/users',
data: newUser.toJson(),
fromJson: (json) => User.fromJson(json),
invalidateListCache: true, // Clear related list caches
);
result.fold(
(error) => showError('Failed to create user: ${error.message}'),
(success) => showSuccess('User created: ${success.dataFromApi?.name}'),
);
PUT Request
Used for updating existing resources with automatic cache updates.
final updatedUser = user.copyWith(name: 'John Updated');
final result = await EitherApiService.put<User>(
endpoint: '/users/${user.id}',
data: updatedUser.toJson(),
fromJson: (json) => User.fromJson(json),
updateCache: true, // Update cache with new data
);
result.fold(
(error) => handleError(error),
(success) => updateUI(success.dataFromApi),
);
DELETE Request
Used for removing resources with automatic cache cleanup.
final result = await EitherApiService.delete<void>(
endpoint: '/users/${user.id}',
invalidateCache: true, // Remove from cache
);
result.fold(
(error) => showError('Delete failed: ${error.message}'),
(success) => showSuccess('User deleted successfully'),
);
Key Parameters
Common Parameters
| Parameter | Type | Description |
|---|---|---|
endpoint |
String |
API endpoint path (required) |
fromJson |
T Function(dynamic)? |
Function to convert JSON to your model |
queryParameters |
Map<String, dynamic>? |
URL query parameters |
options |
Options? |
Dio request options (headers, timeout, etc.) |
cancelToken |
CancelToken? |
Token to cancel the request |
Caching Parameters
| Parameter | Type | Description |
|---|---|---|
enableCachingRequest |
bool? |
Override global caching for GET requests |
invalidateListCache |
bool? |
Clear related list cache after POST |
updateCache |
bool? |
Update cache with new data after PUT |
invalidateCache |
bool? |
Remove cached data after DELETE |
Progress Tracking
| Parameter | Type | Description |
|---|---|---|
onReceiveProgress |
ProgressCallback? |
Track download progress |
onSendProgress |
ProgressCallback? |
Track upload progress |
Error Handling Patterns
Using fold() Method
final result = await EitherApiService.get<User>(endpoint: '/user/123');
result.fold(
(error) {
// Handle different error types
switch (error.type) {
case ErrorType.noInternet:
showRetryDialog();
break;
case ErrorType.unauthorized:
redirectToLogin();
break;
case ErrorType.serverError:
showMaintenanceMessage();
break;
default:
showGenericError(error.message);
}
},
(success) {
// Handle success
final user = success.dataFromApi;
updateUserProfile(user);
},
);
Using isLeft/isRight Properties
final result = await EitherApiService.get<User>(endpoint: '/user/123');
if (result.isLeft) {
final error = result.left;
print('Request failed: ${error.message}');
// Access detailed error info
print('Status Code: ${error.statusCode}');
print('Error Type: ${error.type.name}');
if (error.hasDetails) {
print('Request URL: ${error.details!.fullUrl}');
print('Duration: ${error.details!.calculatedDuration}');
}
} else {
final success = result.right;
final user = success.dataFromApi;
// Access response details
print('Status: ${success.dioDetails.statusCode}');
print('Duration: ${success.dioDetails.calculatedDuration}ms');
print('From Cache: ${success.dioDetails.extra.containsKey('cached')}');
}
Advanced Usage
Custom Error Handling
Future<User?> getUserSafely(int userId) async {
final result = await EitherApiService.get<User>(
endpoint: '/users/$userId',
fromJson: (json) => User.fromJson(json),
);
return result.fold(
(error) {
// Log error for debugging
logger.error('Failed to get user $userId: ${error.message}');
// Return null for safe handling
return null;
},
(success) => success.dataFromApi,
);
}
Chaining Requests
Future<Either<ServiceErrorModel, UserProfile>> getUserProfile(int userId) async {
final userResult = await EitherApiService.get<User>(
endpoint: '/users/$userId',
fromJson: (json) => User.fromJson(json),
);
if (userResult.isLeft) {
return Left(userResult.left);
}
final user = userResult.right.dataFromApi!;
final profileResult = await EitherApiService.get<Profile>(
endpoint: '/users/$userId/profile',
fromJson: (json) => Profile.fromJson(json),
);
return profileResult.fold(
(error) => Left(error),
(success) => Right(UserProfile(
user: user,
profile: success.dataFromApi!,
)),
);
}
Batch Operations
Future<List<User>> getMultipleUsers(List<int> userIds) async {
final futures = userIds.map((id) => EitherApiService.get<User>(
endpoint: '/users/$id',
fromJson: (json) => User.fromJson(json),
));
final results = await Future.wait(futures);
return results
.where((result) => result.isRight)
.map((result) => result.right.dataFromApi!)
.toList();
}
Initialization Options
await EitherApiService.init(
// Required
baseUrl: 'https://api.example.com',
// Caching
boxNameCaching: 'api_cache', // Cache storage name
enableCaching: true, // Enable/disable caching
// Authentication
getToken: () => AuthService.getToken(), // Custom token provider
onUnauthorized: () => AuthService.logout(), // 401 handler
// Network
connectTimeout: Duration(seconds: 15),
receiveTimeout: Duration(seconds: 30),
// Headers
headers: {
'App-Version': '1.0.0',
'Platform': Platform.isIOS ? 'ios' : 'android',
},
// Debug
enableLogging: kDebugMode, // Enable in debug mode only
);
Best Practices
-
Always handle both sides of Either
// Good result.fold( (error) => handleError(error), (success) => handleSuccess(success), ); // Avoid if (result.isRight) { // Only handling success } -
Use meaningful error messages
result.fold( (error) => showSnackbar('Failed to save changes. Please try again.'), (success) => showSnackbar('Changes saved successfully!'), ); -
Leverage caching appropriately
// Cache frequently accessed, rarely changing data final staticData = await EitherApiService.get<Config>( endpoint: '/config', enableCachingRequest: true, ); // Don't cache dynamic or user-specific data final notifications = await EitherApiService.get<List<Notification>>( endpoint: '/notifications', enableCachingRequest: false, ); -
Use proper generic types
// Good - specific type EitherApiService.get<List<User>>() // Avoid - dynamic type EitherApiService.get()
This pattern ensures predictable error handling while maintaining clean, readable code!
Libraries
- api_flow
- Src/Config/api_interceptor
- Src/Config/either_pattern
- Src/Config/Models/dio_response_details
- Src/Config/Models/responses_results_model
- Src/Core/Data/Api/api_server
- Src/Core/Data/Cache/api_caching_service
- Src/Core/Data/Cache/local_storage_keys
- Src/Core/Data/Cache/local_storage_service
- Src/Core/Errors/error_type
- Src/Core/Errors/service_error
- Src/Core/Service/either_api_service