Flutter SmartDio
A transport-agnostic HTTP wrapper that enhances ANY HTTP client with offline caching, request queuing, retry mechanisms, and comprehensive logging.
Features โข Installation โข Quick Start โข Documentation โข Example
๐ 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';
// Initialize with built-in dart:io HttpClient (default)
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, // Survives app restarts
  ),
  cacheStore: HiveCacheStore(), // Persistent cache
);
// Or use with Dio
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, // Enable detailed response logging
    enableMetrics: true,
    enableDeduplication: true,
    enableRequestQueue: true,
    queueStorageType: QueueStorageType.persistent, // Default, survives app restarts
    maxQueueSize: 100,
    maxQueueAge: Duration(days: 7),
  ),
  cacheStore: HiveCacheStore(),
);
2. Making Requests
// Type-safe GET request
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}'),
);
// POST request with caching
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>,
);
// All HTTP methods are supported
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
// Start with dart:io HttpClient
final client = SmartDioClient(
  adapter: HttpClientAdapterImpl(),
  config: const SmartDioConfig(
    defaultTimeout: Duration(seconds: 30),
    enableMetrics: true,
  ),
);
// Switch to Dio - same API!
await client.dispose();
import 'package:dio/dio.dart';
final newClient = SmartDioClient(
  adapter: DioClientAdapter(dioInstance: Dio()),
  config: const SmartDioConfig(
    defaultTimeout: Duration(seconds: 30),
    enableMetrics: true,
  ),
);
// Or use HTTP package
import 'package:http/http.dart' as http;
final httpPackageClient = SmartDioClient(
  adapter: HttpPackageAdapter(httpClient: http.Client()),
  config: const SmartDioConfig(
    defaultTimeout: Duration(seconds: 30),
    enableMetrics: true,
  ),
);
// Or use Chopper
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
// Network-first (default)
CachePolicy.networkFirst(ttl: Duration(minutes: 5))
// Cache-first (offline-friendly)
CachePolicy.cacheFirst(ttl: Duration(hours: 1))
// Cache-only (no network)
CachePolicy.cacheOnly()
// Network-only (no cache)
CachePolicy.networkOnly()
// No caching
CachePolicy.none()
Retry Policies
// Exponential backoff (default configuration)
const RetryPolicy.exponentialBackoff(
  maxAttempts: 3,
  initialDelay: Duration(milliseconds: 500),
  multiplier: 2.0,
  jitter: true,
)
// Fixed delay
const RetryPolicy.fixed(
  maxAttempts: 3,
  delay: Duration(seconds: 1),
)
// Custom retry logic
final RetryPolicy.custom(
  maxAttempts: 5,
  delayCalculator: (attempt) => Duration(seconds: attempt * 2),
  shouldRetry: (error) => error.type == SmartDioErrorType.network,
)
// No retry
const RetryPolicy.none()
Queue Storage Options
// Persistent storage (default) - survives app restarts
SmartDioConfig(
  queueStorageType: QueueStorageType.persistent,
  maxQueueSize: 100,
  maxQueueAge: Duration(days: 7),
)
// Memory-only storage - lost on app restart
SmartDioConfig(
  queueStorageType: QueueStorageType.memory,
  maxQueueSize: 50,
)
// Disable queuing entirely
SmartDioConfig(
  queueStorageType: QueueStorageType.none,
  enableRequestQueue: false,
)
Enhanced Debug Logging
// Enable detailed logging to see actual response data
SmartDioConfig(
  logLevel: LogLevel.debug, // Shows both raw JSON and transformed objects
)
// Example output:
// ๐ฅ Raw Response Data:
// {
//   "id": 1,
//   "name": "John Doe",
//   "email": "john@example.com"
// }
// ๐ Transformed Response:
// User(id: 1, name: John Doe, email: john@example.com)
๐ Monitoring & Analytics
// Listen to performance metrics
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;
  }
});
// Get real-time statistics
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');
// Get queue metrics
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,  // Framework operations logging
    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),  // HTTP traffic logging
  config: const SmartDioConfig(
    // ... other config
  ),
);
๐ 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),  // Full HTTP details
  config: const SmartDioConfig(
    logLevel: LogLevel.debug,  // All framework details
    enableMetrics: true,
  ),
);
Production Mode
final client = SmartDioClient(
  adapter: DioClientAdapter(dioInstance: Dio()),
  logger: SmartLogger(level: LogLevel.warning),  // Errors only
  config: const SmartDioConfig(
    logLevel: LogLevel.warning,  // Framework errors only
    enableMetrics: true,
  ),
);
Performance Testing
final client = SmartDioClient(
  adapter: DioClientAdapter(dioInstance: Dio()),
  logger: SmartLogger(level: LogLevel.error),  // Minimal logging
  config: const SmartDioConfig(
    logLevel: LogLevel.error,  // Minimal logging
    enableMetrics: false,  // Disable for pure performance
  ),
);
๐จ 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
 
- Headers: 
- Nested Object Support: Deep sanitization of complex JSON structures
- Performance Optimized: Colors only applied when using ColorfulConsoleLogSink
๐๏ธ Custom Logger Configuration
// Basic logger (no colors/emojis)
final basicLogger = SmartLogger.basic(level: LogLevel.debug);
// Compact logger (minimal output)
final compactLogger = SmartLogger.compact(level: LogLevel.debug);
// Custom logger with specific settings
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) {
    // Handle success
    final data = success.data;
    final fromCache = success.isFromCache;
    final statusCode = success.statusCode;
  },
  (error) {
    // Handle different error types
    switch (error.type) {
      case SmartDioErrorType.network:
        showNetworkError();
        break;
      case SmartDioErrorType.timeout:
        showTimeoutError();
        break;
      case SmartDioErrorType.badResponse:
        showServerError(error.statusCode);
        break;
    }
  },
);
Offline Queue Management
// Enable offline queueing (enabled by default)
final client = SmartDioClient(
  adapter: HttpClientAdapterImpl(),
  config: const SmartDioConfig(
    enableRequestQueue: true,
    maxQueueSize: 100,
  ),
  requestQueue: RequestQueue(
    storage: MemoryQueueStorage(),
    maxSize: 50,
  ),
);
// Listen to queue events
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;
  }
});
// Manually control offline mode
client.connectivity.setManualOfflineMode(true);  // Force offline
client.connectivity.setManualOfflineMode(false); // Back online
// Check connectivity status
final connectivityInfo = client.connectivity.currentStatus;
print('Status: ${connectivityInfo.status}');
print('Quality: ${connectivityInfo.quality}');
๐งช Testing
SmartDio is designed to be test-friendly with easy mocking:
// Create a mock adapter for testing
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 {}
}
// Use in tests
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        # Interactive multi-client demo
# or
flutter run lib/example2.dart    # Simple API demo
๐๏ธ 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!
Libraries
- flutter_smartdio
- A transport-agnostic HTTP wrapper that enhances ANY HTTP client with offline caching, request queuing, retry mechanisms, and comprehensive logging.
- main