Flutter SmartDio
🚀 Features
🔌 Transport-Agnostic Design
- Works with ANY HTTP client: Dio, http package, Chopper, dart:io HttpClient
- Unified API across all transport layers
- Easy client switching without changing your code
- Adapter pattern for extensibility
💾 Persistent Caching
- Hive-based storage that survives app restarts
- Intelligent TTL management with automatic expiry
- Cache policies: Network-first, Cache-first, Cache-only, Network-only
- Real-time cache statistics and analytics
🔄 Smart Retry & Resilience
- Exponential backoff with configurable delays
- Custom retry policies for different scenarios
- Request deduplication to prevent duplicate calls
- Never-crash philosophy with structured error handling
📱 Offline-First Architecture
- Persistent request queuing with Hive storage that survives app restarts
- Configurable storage options: persistent, memory-only, or disabled
- Automatic queue processing when connectivity returns
- Connectivity monitoring with quality assessment
- Seamless online/offline transitions
📊 Advanced Monitoring
- Performance metrics with response time tracking
- Success rate analytics and failure reporting
- Enhanced response logging that shows actual object data instead of "Instance of Object"
- Dual logging: raw JSON response + transformed object data
- Smart object serialization with automatic toJson() detection
- Colorized debug logging with syntax-highlighted JSON and headers
- Comprehensive logging with sensitive data protection
- Real-time event streaming for monitoring
🎯 Developer Experience
- Type-safe responses with sealed classes
- Clean architecture with dependency injection
- Minimal configuration required
- Extensive documentation and examples
📦 Installation
Add this to your package's pubspec.yaml file:
dependencies:
flutter_smartdio: ^1.0.4
Then run:
flutter pub get
⚡ Quick Start
1. Basic Setup
import 'package:flutter_smartdio/flutter_smartdio.dart';
final client = SmartDioClient(
adapter: HttpClientAdapterImpl(),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
cachePolicy: CachePolicy.networkFirst(
ttl: Duration(minutes: 5),
),
enableMetrics: true,
enableRequestQueue: true,
queueStorageType: QueueStorageType.persistent,
),
cacheStore: HiveCacheStore(),
);
import 'package:dio/dio.dart';
final dioClient = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
retryPolicy: RetryPolicy.exponentialBackoff(
maxAttempts: 3,
initialDelay: Duration(milliseconds: 500),
),
cachePolicy: CachePolicy.networkFirst(ttl: Duration(minutes: 10)),
logLevel: LogLevel.debug,
enableMetrics: true,
enableDeduplication: true,
enableRequestQueue: true,
queueStorageType: QueueStorageType.persistent,
maxQueueSize: 100,
maxQueueAge: Duration(days: 7),
),
cacheStore: HiveCacheStore(),
);
2. Making Requests
final response = await client.get<User>(
'https://jsonplaceholder.typicode.com/users/1',
transformer: (data) => User.fromJson(data as Map<String, dynamic>),
);
response.fold(
(success) => print('User: ${success.data.name}'),
(error) => print('Error: ${error.error}'),
);
final postResponse = await client.post<Map<String, dynamic>>(
'https://jsonplaceholder.typicode.com/posts',
body: {'title': 'Hello World', 'body': 'Test content', 'userId': 1},
config: const RequestConfig(
cachePolicy: CachePolicy.networkFirst(ttl: Duration(hours: 1)),
),
transformer: (data) => data as Map<String, dynamic>,
);
final putResponse = await client.put<Post>(
'https://jsonplaceholder.typicode.com/posts/1',
body: updatedPost.toJson(),
transformer: (data) => Post.fromJson(data as Map<String, dynamic>),
);
final patchResponse = await client.patch<Post>(
'https://jsonplaceholder.typicode.com/posts/1',
body: {'title': 'Updated Title'},
transformer: (data) => Post.fromJson(data as Map<String, dynamic>),
);
final deleteResponse = await client.delete<Map<String, dynamic>>(
'https://jsonplaceholder.typicode.com/posts/1',
transformer: (data) => data as Map<String, dynamic>? ?? {},
);
3. Switch HTTP Clients Seamlessly
final client = SmartDioClient(
adapter: HttpClientAdapterImpl(),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
enableMetrics: true,
),
);
await client.dispose();
import 'package:dio/dio.dart';
final newClient = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
enableMetrics: true,
),
);
import 'package:http/http.dart' as http;
final httpPackageClient = SmartDioClient(
adapter: HttpPackageAdapter(httpClient: http.Client()),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
enableMetrics: true,
),
);
import 'package:chopper/chopper.dart';
final chopperClient = SmartDioClient(
adapter: ChopperClientAdapter(client: ChopperClient()),
config: const SmartDioConfig(
defaultTimeout: Duration(seconds: 30),
enableMetrics: true,
),
);
🏗️ Supported HTTP Clients
| Client |
Adapter Class |
Package |
| dart:io HttpClient |
HttpClientAdapterImpl |
Built-in (Default) |
| Dio |
DioClientAdapter |
dio: ^5.8.0 |
| HTTP Package |
HttpPackageAdapter |
http: ^1.4.0 |
| Chopper |
ChopperClientAdapter |
chopper: ^8.3.0 |
🎛️ Configuration Options
Cache Policies
CachePolicy.networkFirst(ttl: Duration(minutes: 5))
CachePolicy.cacheFirst(ttl: Duration(hours: 1))
CachePolicy.cacheOnly()
CachePolicy.networkOnly()
CachePolicy.none()
Retry Policies
const RetryPolicy.exponentialBackoff(
maxAttempts: 3,
initialDelay: Duration(milliseconds: 500),
multiplier: 2.0,
jitter: true,
)
const RetryPolicy.fixed(
maxAttempts: 3,
delay: Duration(seconds: 1),
)
final RetryPolicy.custom(
maxAttempts: 5,
delayCalculator: (attempt) => Duration(seconds: attempt * 2),
shouldRetry: (error) => error.type == SmartDioErrorType.network,
)
const RetryPolicy.none()
Queue Storage Options
SmartDioConfig(
queueStorageType: QueueStorageType.persistent,
maxQueueSize: 100,
maxQueueAge: Duration(days: 7),
)
SmartDioConfig(
queueStorageType: QueueStorageType.memory,
maxQueueSize: 50,
)
SmartDioConfig(
queueStorageType: QueueStorageType.none,
enableRequestQueue: false,
)
Enhanced Debug Logging
SmartDioConfig(
logLevel: LogLevel.debug,
)
📊 Monitoring & Analytics
client.metrics.events.listen((event) {
switch (event) {
case RequestCompletedEvent(:final metrics):
print('Request took: ${metrics.totalDuration.inMilliseconds}ms');
print('Success: ${metrics.success}');
break;
case CacheHitEvent():
print('Cache hit occurred');
break;
case CacheMissEvent():
print('Cache miss occurred');
break;
}
});
final cacheMetrics = client.metrics.getCacheMetrics();
print('Cache hit rate: ${(cacheMetrics.hitRate * 100).toStringAsFixed(1)}%');
print('Cache hits: ${cacheMetrics.hitCount}');
print('Cache misses: ${cacheMetrics.missCount}');
final successRate = client.metrics.getSuccessRate();
print('Overall success rate: ${(successRate * 100).toStringAsFixed(1)}%');
final avgResponseTime = client.metrics.getAverageResponseTime();
print('Average response time: ${avgResponseTime.inMilliseconds}ms');
final queueMetrics = client.metrics.getQueueMetrics(client.queue.length);
print('Queue size: ${queueMetrics.currentSize}');
print('Queue processed: ${queueMetrics.totalProcessed}');
print('Queue success rate: ${(queueMetrics.successRate * 100).toStringAsFixed(1)}%');
🎨 Enhanced Debug Logging
SmartDio includes a powerful, colorized logging system that provides detailed insights into your HTTP traffic. Perfect for development and debugging!
🌈 Colorized Console Output
When debug logging is enabled, you'll see beautiful, color-coded logs with:
- 📋 Headers - Blue keys, Green values, Red for sensitive data
- 📤 Request Payload - Syntax-highlighted JSON with proper indentation
- 📥 Response Body - Color-coded data types (strings, numbers, booleans)
- 🔒 Security - Sensitive data automatically redacted as
[REDACTED]
🔧 Enable Debug Logging
There are two LogLevel settings that control different aspects of logging:
1. Framework-Level Logging (SmartDioConfig)
Controls internal client operations like cache hits, retries, queue operations:
final client = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
config: const SmartDioConfig(
logLevel: LogLevel.debug,
enableMetrics: true,
enableDeduplication: true,
enableRequestQueue: true,
),
);
2. HTTP Traffic Logging (SmartLogger)
Controls detailed HTTP request/response logging with headers, payloads, and response bodies:
final client = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
logger: SmartLogger(level: LogLevel.debug),
config: const SmartDioConfig(
),
);
📊 Log Level Comparison
| Aspect |
SmartDioConfig.logLevel |
SmartLogger.level |
| Scope |
Client framework operations |
HTTP traffic details |
| Controls |
Cache, retry, queue logs |
Headers, payloads, responses |
| Example Logs |
"Cache hit", "Retrying request" |
Request headers, JSON payloads |
| Performance Impact |
Low |
Higher (detailed HTTP data) |
| Security |
General client info |
Sensitive data (sanitized) |
🎯 Recommended Settings
Development Mode
final client = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
logger: SmartLogger(level: LogLevel.debug),
config: const SmartDioConfig(
logLevel: LogLevel.debug,
enableMetrics: true,
),
);
Production Mode
final client = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
logger: SmartLogger(level: LogLevel.warning),
config: const SmartDioConfig(
logLevel: LogLevel.warning,
enableMetrics: true,
),
);
Performance Testing
final client = SmartDioClient(
adapter: DioClientAdapter(dioInstance: Dio()),
logger: SmartLogger(level: LogLevel.error),
config: const SmartDioConfig(
logLevel: LogLevel.error,
enableMetrics: false,
),
);
🎨 Example Debug Output
When both log levels are set to LogLevel.debug, you'll see output like:
🔧 [DEBUG] 📋 Request Headers:
content-type: application/json
authorization: [REDACTED]
user-agent: SmartDio Flutter App/1.0
🔧 [DEBUG] 📤 Request Payload:
{
"title": "Hello World",
"body": "Test content",
"userId": 1,
"password": "[REDACTED]"
}
✅ [INFO] HTTP Response: 201 POST /api/posts (245ms)
🔧 [DEBUG] 📋 Response Headers:
content-type: application/json; charset=utf-8
server: nginx/1.18.0
location: /api/posts/101
🔧 [DEBUG] 📥 Response Body:
{
"id": 101,
"title": "Hello World",
"body": "Test content",
"userId": 1,
"createdAt": "2023-12-01T10:30:00Z"
}
🛡️ Security Features
- Automatic Sanitization: Sensitive headers and body fields are automatically redacted
- Configurable Sensitive Fields:
- Headers:
authorization, x-api-key, cookie, x-auth-token
- Body:
password, token, secret, key, authorization
- Nested Object Support: Deep sanitization of complex JSON structures
- Performance Optimized: Colors only applied when using ColorfulConsoleLogSink
🎛️ Custom Logger Configuration
final basicLogger = SmartLogger.basic(level: LogLevel.debug);
final compactLogger = SmartLogger.compact(level: LogLevel.debug);
final customLogger = SmartLogger(
level: LogLevel.debug,
sensitiveHeaders: ['custom-auth', 'x-secret'],
sensitiveBodyFields: ['customPassword', 'apiKey'],
sinks: [ColorfulConsoleLogSink(
enableColors: true,
enableEmojis: true,
compactMode: false,
)],
);
🔧 Advanced Usage
Custom Error Handling
final response = await client.get<Data>('/api/data',
transformer: (data) => Data.fromJson(data),
);
response.fold(
(success) {
final data = success.data;
final fromCache = success.isFromCache;
final statusCode = success.statusCode;
},
(error) {
switch (error.type) {
case SmartDioErrorType.network:
showNetworkError();
break;
case SmartDioErrorType.timeout:
showTimeoutError();
break;
case SmartDioErrorType.badResponse:
showServerError(error.statusCode);
break;
}
},
);
Offline Queue Management
final client = SmartDioClient(
adapter: HttpClientAdapterImpl(),
config: const SmartDioConfig(
enableRequestQueue: true,
maxQueueSize: 100,
),
requestQueue: RequestQueue(
storage: MemoryQueueStorage(),
maxSize: 50,
),
);
client.queue.events.listen((event) {
switch (event) {
case QueueItemAdded():
print('Request queued for later');
break;
case QueueItemRemoved():
print('Request removed from queue');
break;
case QueueItemFailed():
print('Queued request failed');
break;
}
});
client.connectivity.setManualOfflineMode(true);
client.connectivity.setManualOfflineMode(false);
final connectivityInfo = client.connectivity.currentStatus;
print('Status: ${connectivityInfo.status}');
print('Quality: ${connectivityInfo.quality}');
🧪 Testing
SmartDio is designed to be test-friendly with easy mocking:
class MockAdapter extends HttpClientAdapter {
final Map<String, dynamic> mockResponses;
MockAdapter(this.mockResponses);
@override
Future<SmartDioResponse<T>> execute<T>({
required SmartDioRequest request,
required T Function(dynamic data) transformer,
}) async {
final mockData = mockResponses[request.uri.toString()];
if (mockData == null) {
return SmartDioError<T>(
error: Exception('No mock response found'),
type: SmartDioErrorType.network,
correlationId: request.correlationId,
timestamp: DateTime.now(),
duration: Duration.zero,
);
}
return SmartDioSuccess<T>(
data: transformer(mockData),
statusCode: 200,
correlationId: request.correlationId,
timestamp: DateTime.now(),
duration: Duration(milliseconds: 100),
);
}
@override
Future<void> close() async {}
}
final mockClient = SmartDioClient(
adapter: MockAdapter({
'https://api.example.com/users/1': {
'id': 1,
'name': 'Test User',
'email': 'test@example.com'
}
}),
config: const SmartDioConfig(),
);
final response = await mockClient.get<User>(
'https://api.example.com/users/1',
transformer: (data) => User.fromJson(data as Map<String, dynamic>),
);
📱 Example Apps
The package includes two comprehensive example apps:
Main Example (example/lib/main.dart)
- Multi-HTTP client switching (Dio, HTTP, Chopper, dart:io HttpClient)
- Interactive feature testing with live UI
- Real-time performance metrics and analytics
- Persistent cache management with Hive
- Colorized debug logging with headers, payloads, and response bodies
- Enhanced logging with security-safe sensitive data redaction
- Offline mode simulation and queue management
- Request deduplication testing
- Type-safe API demonstrations
Simple API Demo (example/lib/example2.dart)
- Clean API service implementation
- All HTTP methods (GET, POST, PUT, PATCH, DELETE)
- Type-safe model examples (User, Post, Comment)
- Cache strategies demonstration
- Error handling patterns
- Performance metrics integration
cd example
flutter run lib/main.dart
flutter run lib/example2.dart
🏛️ Architecture
SmartDio uses clean architecture principles:
┌─────────────────────────────────────────┐
│ SmartDioClient │
├─────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Config │ │ Interceptors │ │
│ └─────────────┘ └─────────────────────┘ │
├─────────────────────────────────────────┤
│ ┌─────────────┐ ┌──────────┐ ┌────────┐ │
│ │ Cache │ │ Queue │ │ Logger │ │
│ └─────────────┘ └──────────┘ └────────┘ │
├─────────────────────────────────────────┤
│ HTTP Adapters │
│ ┌──────┐ ┌──────┐ ┌─────────┐ ┌──────┐ │
│ │ Dio │ │ HTTP │ │ Chopper │ │ dart │ │
│ └──────┘ └──────┘ └─────────┘ └──────┘ │
└─────────────────────────────────────────┘
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide first.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
🐛 Issues
If you encounter any issues, please create an issue with:
- Flutter version
- Dart version
- SmartDio version
- Minimal reproduction code
- Error logs
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🙏 Acknowledgments
- Inspired by the need for a universal HTTP solution in Flutter
- Built with ❤️ for the Flutter community
- Thanks to all HTTP client library authors for their excellent work
🔗 Links
Made with ❤️ by Rahul Shah
If you find this package helpful, please ⭐ the repository!