dio_flow 1.3.3
dio_flow: ^1.3.3 copied to clipboard
Flexible HTTP client for Flutter, built on Dio with mock support, GraphQL, file handling, caching, and token management.
๐ Dio Flow #
A powerful, production-ready Flutter package that supercharges Dio HTTP client
Built for modern Flutter applications that demand robust, scalable API integration
๐ Documentation โข ๐ Quick Start โข ๐ก Examples โข ๐ Issues
๐ฏ Why Dio Flow? #
Dio Flow transforms your API integration experience by providing enterprise-grade features out of the box:
- ๐ฅ Zero Configuration: Get started in minutes with sensible defaults
- ๐ก๏ธ Production Ready: Battle-tested with comprehensive error handling
- ๐ Performance First: Built-in caching, retry logic, and request optimization
- ๐ Universal: Works seamlessly across all Flutter platforms
- ๐งช Developer Friendly: Extensive mocking support for testing
- ๐ Observable: Built-in metrics and detailed logging
๐ Table of Contents #
- ๐ฏ Why Dio Flow?
- โจ Features
- ๐ฏ Platform Support
- ๐ฆ Installation
- ๐ Getting Started
- ๐ฏ Core Components
- ๏ฟฝ Authentication
- ๐ Endpoint Configuration
- ๐ Advanced Features
- ๏ฟฝ๏ธ uBest Practices
- ๐งช Mock Support
- ๏ฟฝ GraphQL Support
- ๏ฟฝ FBile Operations
- ๐ Troubleshooting
- ๐ค Contributing
- ๏ฟฝ LicenseOse
โจ Features #
๐ Core | ๐ Security | ๐ ๏ธ Developer Experience |
---|---|---|
Modern HTTP Client | Token Management | Type-Safe Responses |
Smart Response Handling | Auto Token Refresh | Comprehensive Testing |
Intelligent Caching | Request Authentication | Built-in Mocking |
Auto-Retry Logic | Secure Token Storage | Detailed Logging |
๐ Platform | ๐ Performance | ๐ง Advanced |
---|---|---|
Universal Support | Request Metrics | GraphQL Support |
Web Compatible | Rate Limiting | File Operations |
Cross-Platform | Network Awareness | Pagination Utils |
WASM Ready | Connection Pooling | JSON Utilities |
๐ฏ Key Capabilities #
- ๐ Modern HTTP Client: Enhanced Dio with production-grade features
- ๐ Smart Response Handling: Automatic conversion to strongly-typed models
- ๐พ Intelligent Caching: Configurable TTL with automatic invalidation
- ๐ Token Management: Robust authentication with automatic refresh
- ๐ Auto-Retry: Configurable retry logic with exponential backoff
- โก Rate Limiting: Built-in throttling to prevent API abuse
- ๐ถ Network Awareness: Automatic connectivity change handling
- ๐ Request Metrics: Built-in performance tracking and analytics
- ๐ Detailed Logging: Complete request/response logging with cURL commands
- ๐ Pagination Support: Utilities for handling paginated API responses
- ๐ก๏ธ Type Safety: Strong typing throughout the entire library
- ๐ฏ Error Handling: Comprehensive error handling with typed responses
- ๐งช Mock Support: Built-in mocking system for testing without real HTTP calls
- ๐ GraphQL Support: Native GraphQL queries, mutations, and subscriptions
- ๐ File Operations: Easy file upload/download with progress tracking
- ๐ง Extensible Architecture: Plugin-based design for custom functionality
๐ฏ Platform Support #
๐ Universal Flutter Support - Write Once, Run Everywhere #
Platform | Support | File Operations | Network Checking | WASM Compatible |
---|---|---|---|---|
๐ฑ iOS | โ Full | โ Complete | โ Native | โ Ready |
๐ค Android | โ Full | โ Complete | โ Native | โ Ready |
๐ Web | โ Full | ๐ Bytes Only* | โ HTTP-based | โ Compatible |
๐ช Windows | โ Full | โ Complete | โ Native | โ Ready |
๐ macOS | โ Full | โ Complete | โ Native | โ Ready |
๐ง Linux | โ Full | โ Complete | โ Native | โ Ready |
๐ก Web Platform Note: File operations use
Uint8List
instead ofFile
objects due to browser security restrictions. This ensures maximum compatibility while maintaining functionality.
๐ฆ Installation #
Get started in seconds with a single dependency #
๐ Add Dependency #
dependencies:
dio_flow: ^1.3.3
๐ Install #
flutter pub get
๐ฑ Import #
import 'package:dio_flow/dio_flow.dart';
๐ฆ Development Dependencies (Optional)
For enhanced development experience:
dev_dependencies:
dio_flow: ^1.3.3
# For testing with mocks
mockito: ^5.4.0
# For integration testing
integration_test:
sdk: flutter
๐ Getting Started #
โก From zero to API calls in under 2 minutes #
๐ฏ Quick Setup #
import 'package:dio_flow/dio_flow.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ๐ง Configure Dio Flow
DioFlowConfig.initialize(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
);
// ๐ Initialize the client
await ApiClient.initialize();
runApp(MyApp());
}
๐ฏ Your First API Call #
// ๐ก Make your first request
final response = await DioRequestHandler.get('users');
if (response is SuccessResponseModel) {
print('โ
Success: ${response.data}');
} else {
print('โ Error: ${response.error?.message}');
}
๐ง Advanced Configuration #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ๐ง Advanced configuration
DioFlowConfig.initialize(
baseUrl: 'https://api.example.com',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
sendTimeout: const Duration(seconds: 30),
// ๐ Enable detailed logging
debugMode: true,
// ๐ Security headers
defaultHeaders: {
'User-Agent': 'MyApp/1.0.0',
'Accept': 'application/json',
},
// ๐ Retry configuration
retryOptions: RetryOptions(
maxAttempts: 3,
retryInterval: const Duration(seconds: 2),
),
);
// ๐ Initialize token management
await TokenManager.initialize();
// ๐ Initialize API client
await ApiClient.initialize();
runApp(MyApp());
}
๐ฏ Core Components #
DioRequestHandler #
The main class for making HTTP requests:
// GET request
final response = await DioRequestHandler.get(
'users',
parameters: {'role': 'admin'},
requestOptions: RequestOptionsModel(
hasBearerToken: true,
shouldCache: true,
retryOptions: RetryOptions(
maxAttempts: 3,
retryInterval: const Duration(seconds: 1),
),
),
);
// POST request with typed response
final loginResponse = await DioRequestHandler.post<LoginResponse>(
'auth/login',
data: {
'email': 'user@example.com',
'password': '********',
},
requestOptions: RequestOptionsModel(
hasBearerToken: false,
),
);
Response Models #
All responses are wrapped in typed models:
if (response is SuccessResponseModel) {
final data = response.data;
// Handle success
} else {
final error = response.error;
switch (error.errorType) {
case ErrorType.network:
// Handle network error
break;
case ErrorType.validation:
// Handle validation error
break;
case ErrorType.unauthorized:
// Handle auth error
break;
// ... handle other error types
}
}
Interceptors #
The package includes several built-in interceptors:
- MetricsInterceptor: Tracks request performance
- RateLimitInterceptor: Prevents API throttling
- DioInterceptor: Handles authentication and headers
- RetryInterceptor: Manages request retries
- ConnectivityInterceptor: Handles network state
- CacheInterceptor: Manages response caching
๐ Authentication #
๐ก๏ธ Enterprise-grade authentication with automatic token management #
Dio Flow provides a comprehensive authentication system with persistent storage, automatic token refresh, and seamless integration:
// Initialize token manager (call this in your main.dart)
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await TokenManager.initialize();
// Register refresh logic (consumer-side). Optional โ if you don't set it,
// automatic refresh will be disabled and TokenManager will return null on expired token.
TokenManager.setRefreshHandler((refreshToken) async {
// Use DioRequestHandler to call refresh endpoint (consumer controls endpoint details).
final refreshResp = await DioRequestHandler.post(
'auth/refresh',
data: {'refresh_token': refreshToken},
);
if (refreshResp is! SuccessResponseModel) {
throw ApiException('Refresh failed: ${refreshResp.error?.message ?? 'unknown'}');
}
final data = refreshResp.data!;
final expiresIn = (data['expires_in'] as int?) ?? 3600;
return RefreshTokenResponse(
accessToken: data['access_token'] as String,
refreshToken: data['refresh_token'] as String,
expiry: DateTime.now().add(Duration(seconds: expiresIn)),
);
});
runApp(MyApp());
}
// Setting tokens with persistence
await TokenManager.setTokens(
accessToken: 'your_access_token',
refreshToken: 'your_refresh_token',
expiry: DateTime.now().add(Duration(hours: 1)),
);
// Check presence/validity (auto-refresh performed if expired and handler is set)
final hasToken = await TokenManager.hasAccessToken(); // returns true/false
// Getting access token (will automatically refresh if expired and a handler is configured)
final token = await TokenManager.getAccessToken(); // can be null if no handler and expired
// Clearing tokens
await TokenManager.clearTokens();
Key features:
-
Persistent token storage using SharedPreferences (tokens survive app restarts).
-
RefreshTokenHandler typedef โ consumer provides a small callback that receives the refresh token and returns a RefreshTokenResponse (access, refresh, expiry). This keeps the package network-agnostic.
-
TokenManager.setRefreshHandler(...) โ register refresh logic at startup or runtime (optional).
-
Automatic refresh behavior โ hasAccessToken() and getAccessToken() will automatically attempt to refresh when the access token is expired only if a refresh handler has been set. If no handler is provided, expired tokens are treated as absent (methods return false/null).
-Single-flight refresh protection โ internal _refreshCompleter ensures only one refresh request runs at a time; concurrent callers wait for that single result.
-
Tokens returned by the refresh handler are persisted via setTokens() (keeps state consistent across restarts).
-
All token operations are asynchronous for non-blocking startup and safer IO.
-
getAccessToken() behavior is unified: it either returns a valid token, triggers refresh (if handler exists), or returns null (if expired and no handler).
Protected Requests #
// Make authenticated request
final response = await DioRequestHandler.get(
'user/profile',
requestOptions: RequestOptionsModel(
hasBearerToken: true, // This will automatically include the token
),
);
// Handle token expiration
if (response.error?.errorType == ErrorType.unauthorized) {
// Token expired, handle refresh or logout
}
๐ Endpoint Configuration #
Basic Endpoints #
// Register endpoints
EndpointProvider.instance.register('login', '/auth/login');
EndpointProvider.instance.register('users', '/api/users');
// Use registered endpoints
final response = await DioRequestHandler.post(
'login',
data: {'email': email, 'password': password},
);
Dynamic Endpoints #
// Register endpoint with parameters
EndpointProvider.instance.register('user_details', '/api/users/{id}');
// Use with path parameters
final response = await DioRequestHandler.get(
'user_details',
pathParameters: {'id': '123'},
);
๐ Advanced Features #
Caching #
// Enable caching for a request
final response = await DioRequestHandler.get(
'users',
requestOptions: RequestOptionsModel(
shouldCache: true,
cacheMaxAge: const Duration(minutes: 5),
),
);
// Clear cache
await ApiClient.clearCache();
Pagination #
// Using pagination utilities
final paginatedResponse = await DioRequestHandler.get(
'posts',
parameters: {
'page': 1,
'per_page': 20,
},
);
final pagination = PaginationHelper.fromResponse(paginatedResponse);
final hasMore = pagination.hasNextPage;
final totalPages = pagination.totalPages;
JSON Utilities #
// Safe JSON parsing
final jsonData = JsonUtils.tryParseJson(rawJson);
// Access nested values safely
final nestedValue = JsonUtils.getNestedValue(
jsonData,
'user.profile.name',
'Default Name',
);
Request Queueing #
// Queue multiple requests
final responses = await Future.wait([
DioRequestHandler.get('users'),
DioRequestHandler.get('posts'),
DioRequestHandler.get('comments'),
]);
// Handle rate limiting automatically
final rateLimitedResponse = await DioRequestHandler.get(
'high-frequency-endpoint',
requestOptions: RequestOptionsModel(
shouldRateLimit: true,
rateLimit: 30, // requests per minute
),
);
Custom Response Types #
class PaginatedResponse<T> {
final List<T> items;
final int total;
final int page;
PaginatedResponse.fromJson(
Map<String, dynamic> json,
T Function(Map<String, dynamic>) converter,
) : items = (json['data'] as List)
.map((item) => converter(item))
.toList(),
total = json['total'] ?? 0,
page = json['page'] ?? 1;
}
// Use with typed response
final response = await DioRequestHandler.get<PaginatedResponse<User>>(
'users',
converter: (json) => PaginatedResponse.fromJson(
json,
(item) => User.fromJson(item),
),
);
๐ ๏ธ Best Practices #
๐ก Production-tested patterns for robust API integration #
๐ Essential Patterns #
1. Initialize Early
void main() async {
await ApiClient.initialize();
// ... rest of your app initialization
}
2. Handle Errors Consistently
try {
final response = await DioRequestHandler.get('endpoint');
if (response is SuccessResponseModel) {
// Handle success
} else {
// Use the typed error handling
handleError(response.error);
}
} catch (e) {
// Handle unexpected errors
}
3. Use Type-Safe Responses
class UserResponse {
final String id;
final String name;
UserResponse.fromJson(Map<String, dynamic> json)
: id = json['id'],
name = json['name'];
}
final response = await DioRequestHandler.get<UserResponse>(
'users/me',
converter: (json) => UserResponse.fromJson(json),
);
Error Handling Patterns #
// Create a reusable error handler
Future<T> handleApiResponse<T>(ResponseModel response) async {
if (response is SuccessResponseModel) {
return response.data as T;
}
switch (response.error?.errorType) {
case ErrorType.network:
throw NetworkException(response.error!.message);
case ErrorType.unauthorized:
await handleUnauthorized();
throw AuthException(response.error!.message);
case ErrorType.validation:
throw ValidationException(response.error!.message);
default:
throw ApiException(response.error?.message ?? 'Unknown error');
}
}
// Use in your code
try {
final users = await handleApiResponse<List<User>>(
await DioRequestHandler.get('users'),
);
// Use users data
} on NetworkException catch (e) {
// Handle network error
} on AuthException catch (e) {
// Handle auth error
} on ValidationException catch (e) {
// Handle validation error
} on ApiException catch (e) {
// Handle other API errors
}
๐๏ธ Repository Pattern #
class UserRepository {
Future<User> getCurrentUser() async {
final response = await DioRequestHandler.get<User>(
'users/me',
requestOptions: RequestOptionsModel(
hasBearerToken: true,
shouldCache: true,
cacheMaxAge: const Duration(minutes: 5),
),
converter: (json) => User.fromJson(json),
);
return handleApiResponse<User>(response);
}
Future<void> updateProfile(UserUpdateRequest request) async {
final response = await DioRequestHandler.put(
'users/me',
data: request.toJson(),
requestOptions: RequestOptionsModel(hasBearerToken: true),
);
await handleApiResponse(response);
}
}
โก Performance Tips #
๐ Optimization strategies for production apps
// ๐พ Use caching strategically
final response = await DioRequestHandler.get(
'static-data',
requestOptions: RequestOptionsModel(
shouldCache: true,
cacheMaxAge: const Duration(hours: 1), // Cache static data longer
),
);
// ๐ Batch requests when possible
final futures = [
DioRequestHandler.get('users'),
DioRequestHandler.get('posts'),
DioRequestHandler.get('comments'),
];
final responses = await Future.wait(futures);
// โก Use pagination for large datasets
final paginatedResponse = await PaginationUtils.fetchAllPages(
'large-dataset',
parameters: {'per_page': 50}, // Optimal page size
maxPages: 10, // Limit to prevent memory issues
);
// ๐ฏ Optimize file uploads
final uploadResponse = await FileHandler.uploadBytes(
'upload',
compressedBytes, // Compress before upload
'file.jpg',
onProgress: (sent, total) {
// Update UI efficiently
if (sent % 1024 == 0) { // Update every KB
updateProgressUI(sent / total);
}
},
);
๐งช Mock Support #
๐ญ Powerful mocking system for comprehensive testing #
Dio Flow includes a sophisticated mocking system that makes testing your API integrations effortless:
void main() {
// Enable mock mode
MockDioFlow.enableMockMode();
// Register mock responses
MockDioFlow.mockResponse(
'users',
MockResponse.success([
{'id': 1, 'name': 'John Doe'},
{'id': 2, 'name': 'Jane Smith'},
]),
);
// Register response queue for testing pagination
MockDioFlow.mockResponseQueue('posts', [
MockResponse.success({'page': 1, 'data': [...]}),
MockResponse.success({'page': 2, 'data': [...]}),
]);
// Your tests will now use mocked responses
final response = await DioRequestHandler.get('users');
// Disable mock mode
MockDioFlow.disableMockMode();
}
๐ GraphQL Support #
โก Native GraphQL integration with powerful query building #
Dio Flow provides first-class GraphQL support with an intuitive query builder and comprehensive operation handling:
// Simple query
final response = await GraphQLHandler.query('''
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
''', variables: {'id': '123'});
// Using query builder
final query = GraphQLQueryBuilder.query()
.operationName('GetUsers')
.variables({'first': 'Int'})
.body('users(first: $first) { id name }')
.build();
// Mutations
final mutationResponse = await GraphQLHandler.mutation('''
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
}
}
''', variables: {'input': {'name': 'John', 'email': 'john@example.com'}});
// Batch operations
final operations = [
GraphQLOperation(query: 'query { users { id } }'),
GraphQLOperation(query: 'query { posts { id } }'),
];
final batchResponse = await GraphQLHandler.batch(operations);
๐ File Operations #
๐ค๐ฅ Seamless file handling across all platforms with progress tracking #
Dio Flow provides comprehensive file operations that work consistently across all Flutter platforms, with intelligent platform-specific optimizations:
Mobile/Desktop Platforms (iOS, Android, Windows, macOS, Linux) #
// File upload from File object
final file = File('/path/to/file.jpg');
final uploadResponse = await FileHandler.uploadFile(
'upload',
file,
fieldName: 'avatar',
additionalData: {'userId': '123'},
onProgress: (sent, total) {
print('Upload: ${(sent/total*100).toStringAsFixed(1)}%');
},
);
// File download to disk
final downloadResponse = await FileHandler.downloadFile(
'files/123/download',
'/local/path/file.pdf',
onProgress: (received, total) {
print('Download: ${(received/total*100).toStringAsFixed(1)}%');
},
);
Web Platform #
// Upload from bytes (web-compatible)
final bytesUploadResponse = await FileHandler.uploadBytes(
'upload',
fileBytes, // Uint8List
'filename.jpg',
fieldName: 'avatar',
additionalData: {'userId': '123'},
);
// Download as bytes (web-compatible)
final bytesResponse = await FileHandler.downloadBytes('files/123');
if (bytesResponse is SuccessResponseModel) {
final bytes = bytesResponse.data['bytes'] as Uint8List;
// Use bytes for web download (e.g., trigger browser download)
}
Cross-Platform File Operations #
// Multiple file upload (use bytes for web compatibility)
final files = {
'document': documentBytes, // Uint8List for web
'image': imageBytes, // Uint8List for web
};
final multiUploadResponse = await FileHandler.uploadMultipleFiles(
'upload-multiple',
files,
);
// Upload from bytes (works on all platforms)
final bytesUploadResponse = await FileHandler.uploadBytes(
'upload',
fileBytes,
'filename.jpg',
);
Note: On web platform, direct file system access is restricted by browser security. Use
FileHandler.uploadBytes()
andFileHandler.downloadBytes()
for web-compatible file operations.
๐ Troubleshooting #
๐ ๏ธ Quick solutions to common issues #
๐จ Common Issues & Solutions #
-
Authentication Issues:
- Ensure
hasBearerToken
is set correctly inRequestOptionsModel
- Check if tokens are properly managed in
TokenManager
- Ensure
-
Caching Problems:
- Verify
shouldCache
is enabled in request options - Check cache duration settings
- Try clearing cache with
ApiClient.clearCache()
- Verify
-
Network Errors:
- Check connectivity status
- Verify retry options are configured
- Examine cURL logs for request details
-
Mock Issues:
- Ensure
MockDioFlow.enableMockMode()
is called before requests - Verify mock responses are registered for the correct endpoints
- Check that endpoint paths match exactly
- Ensure
-
GraphQL Errors:
- Validate GraphQL syntax in queries
- Check that variables match the schema
- Ensure the GraphQL endpoint is correctly configured
-
File Upload Issues:
- Verify file permissions and paths
- Check server file size limits
- Ensure correct Content-Type headers for multipart uploads
๐ Debug Mode #
// Enable detailed logging
DioFlowConfig.initialize(
baseUrl: 'https://api.example.com',
debugMode: true, // This will enable detailed logging
);
// Log specific requests
final response = await DioRequestHandler.get(
'users',
requestOptions: RequestOptionsModel(
shouldLogRequest: true, // Log this specific request
),
);
๐ Quick Reference #
โก Common operations at a glance #
๐ API Calls Cheat Sheet
// GET request
final users = await DioRequestHandler.get('users');
// POST with data
final created = await DioRequestHandler.post('users', data: userData);
// PUT update
final updated = await DioRequestHandler.put('users/123', data: updates);
// DELETE
final deleted = await DioRequestHandler.delete('users/123');
// With authentication
final profile = await DioRequestHandler.get(
'profile',
requestOptions: RequestOptionsModel(hasBearerToken: true),
);
// With caching
final cached = await DioRequestHandler.get(
'static-data',
requestOptions: RequestOptionsModel(
shouldCache: true,
cacheMaxAge: Duration(minutes: 30),
),
);
๐ Authentication Cheat Sheet
// Set tokens
await TokenManager.setTokens(
accessToken: 'access_token',
refreshToken: 'refresh_token',
expiry: DateTime.now().add(Duration(hours: 1)),
);
// Check if authenticated
final isAuthenticated = await TokenManager.hasAccessToken();
// Get current token (auto-refreshes if needed)
final token = await TokenManager.getAccessToken();
// Clear tokens (logout)
await TokenManager.clearTokens();
๐ File Operations Cheat Sheet
// Upload file (mobile/desktop)
final upload = await FileHandler.uploadFile('upload', file);
// Upload bytes (all platforms)
final upload = await FileHandler.uploadBytes('upload', bytes, 'file.jpg');
// Download file (mobile/desktop)
final download = await FileHandler.downloadFile('files/123', '/path/file.pdf');
// Download bytes (all platforms)
final download = await FileHandler.downloadBytes('files/123');
๐งช Testing Cheat Sheet
// Enable mocking
MockDioFlow.enableMockMode();
// Mock success response
MockDioFlow.mockResponse('users', MockResponse.success([...]));
// Mock error response
MockDioFlow.mockResponse('users', MockResponse.failure('Error message'));
// Mock network error
MockDioFlow.mockResponse('users', MockResponse.networkError());
// Disable mocking
MockDioFlow.disableMockMode();
๐ค Contributing #
We love contributions! Help make Dio Flow even better ๐ช #
Contributions are welcome! Whether it's:
- ๐ Bug Reports: Found an issue? Let us know!
- โจ Feature Requests: Have an idea? We'd love to hear it!
- ๐ Documentation: Help improve our docs
- ๐ง Code Contributions: Submit a PR!
Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
๐ Contributors #
Thanks to all our amazing contributors who help make Dio Flow better!
๐ License #
๐ MIT License - Free for everyone, everywhere #
This project is licensed under the MIT License - see the LICENSE file for details.
Made with โค๏ธ by the Flutter community
๐ Star us on GitHub if Dio Flow helped you #
Happy coding! ๐