api_flow 2.0.0 copy "api_flow: ^2.0.0" to clipboard
api_flow: ^2.0.0 copied to clipboard

A comprehensive Flutter package for smart API management with Either pattern, local caching, and robust error handling

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 #

  1. Always handle both sides of Either

    // Good
    result.fold(
      (error) => handleError(error),
      (success) => handleSuccess(success),
    );
       
    // Avoid
    if (result.isRight) {
      // Only handling success
    }
    
  2. Use meaningful error messages

    result.fold(
      (error) => showSnackbar('Failed to save changes. Please try again.'),
      (success) => showSnackbar('Changes saved successfully!'),
    );
    
  3. 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,
    );
    
  4. 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!

2
likes
160
points
51
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter package for smart API management with Either pattern, local caching, and robust error handling

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

crypto, dartz, dio, flutter, hive, hive_flutter

More

Packages that depend on api_flow