flutter_smartdio 1.0.4 copy "flutter_smartdio: ^1.0.4" to clipboard
flutter_smartdio: ^1.0.4 copied to clipboard

A transport-agnostic HTTP wrapper that enhances ANY HTTP client with offline caching, request queuing, retry mechanisms, and comprehensive logging.

example/lib/main.dart

import 'dart:io';

import 'package:chopper/chopper.dart';
import 'package:dio/dio.dart' as dio;
import 'package:flutter/material.dart';
import 'package:flutter_smartdio/flutter_smartdio.dart';
import 'package:http/http.dart' as http;
import 'enhanced_logging_example.dart' as logging_example;

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const SmartDioTestApp());
}

class SmartDioTestApp extends StatelessWidget {
  const SmartDioTestApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SmartDio Test App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const SmartDioTestScreen(),
    );
  }
}

class SmartDioTestScreen extends StatefulWidget {
  const SmartDioTestScreen({super.key});

  @override
  State<SmartDioTestScreen> createState() => _SmartDioTestScreenState();
}

enum ClientType { httpClient, dio, httpPackage, chopper }

class _SmartDioTestScreenState extends State<SmartDioTestScreen> {
  late SmartDioClient client;
  final List<String> logs = [];
  final ScrollController _scrollController = ScrollController();
  bool isOfflineMode = false;
  ClientType currentClientType = ClientType.httpClient;

  // HTTP client instances
  late HttpClient httpClient;
  late dio.Dio dioClient;
  late http.Client httpPackageClient;
  late ChopperClient chopperClient;

  // Persistent cache store
  late HiveCacheStore cacheStore;

  @override
  void initState() {
    super.initState();
    _initializeApp();
  }

  Future<void> _initializeApp() async {
    // Initialize cache store
    cacheStore = HiveCacheStore();
    await cacheStore.initialize();
    _addLog('πŸ’Ύ Initialized persistent cache store');

    // Show enhanced logging capabilities
    _demonstrateEnhancedLogging();

    // Initialize HTTP clients
    _initializeClients();

    // Initialize SmartDio client
    _initializeClient();
  }

  void _initializeClients() {
    httpClient = HttpClient();
    dioClient = dio.Dio();
    httpPackageClient = http.Client();
    chopperClient = ChopperClient();
  }

  void _initializeClient() {
    client = SmartDioClient(
      logger: SmartLogger(level: LogLevel.debug),
      adapter: DioClientAdapter(dioInstance: dioClient),
      config: const SmartDioConfig(
        defaultTimeout: Duration(seconds: 10),
        retryPolicy: RetryPolicy.exponentialBackoff(
          maxAttempts: 3,
          initialDelay: Duration(milliseconds: 500),
        ),
        cachePolicy: CachePolicy.networkFirst(
          ttl: Duration(
              minutes: 10), // Increased TTL for better cache persistence
        ),
        logLevel: LogLevel.debug,
        enableMetrics: true,
        enableDeduplication: true,
        enableRequestQueue: true,
      ),
      cacheStore: cacheStore, // Use Hive persistent cache
      requestQueue: RequestQueue(
        storage: MemoryQueueStorage(),
        maxSize: 50,
      ),
    ); // Uses ColorfulConsoleLogSink by default

    // Listen to events
    client.queue.events.listen(_onQueueEvent);
    client.metrics.events.listen(_onMetricsEvent);
    client.connectivity.statusStream.listen(_onConnectivityEvent);
  }

  void _switchClient(ClientType newClientType) async {
    if (newClientType == currentClientType) return;

    _addLog('πŸ”„ Switching to ${_getClientName(newClientType)}...');

    // Dispose current client
    await client.dispose();

    // Update client type
    setState(() {
      currentClientType = newClientType;
    });

    // Initialize new client
    _initializeClient();

    _addLog('βœ… Switched to ${_getClientName(newClientType)}');
  }

  String _getClientName(ClientType type) {
    switch (type) {
      case ClientType.httpClient:
        return 'dart:io HttpClient';
      case ClientType.dio:
        return 'Dio';
      case ClientType.httpPackage:
        return 'http package';
      case ClientType.chopper:
        return 'Chopper';
    }
  }

  void _demonstrateEnhancedLogging() {
    final logger = SmartLogger();

    // Demonstrate different log levels with emojis and colors
    logger.info('🎨 Enhanced SmartLogger initialized with colorful output!');
    logger.debug('πŸ”§ Debug mode active - all HTTP requests will be logged');
    logger.warning('⚠️ This is a warning message example');
    logger.verbose('πŸ” Verbose logging shows detailed information');

    // Demonstrate HTTP-specific logging
    logger.httpRequest('GET', 'https://api.example.com/test',
        correlationId: 'demo-123');
    logger.cacheHit('test-key', age: const Duration(minutes: 5));
    logger.cacheMiss('missing-key', reason: 'expired');

    _addLog(
        '✨ Enhanced logging demonstration complete - check console for colorful output!');
  }

  void _addLog(String message) {
    setState(() {
      logs.add(
          '${DateTime.now().toIso8601String().substring(11, 19)} - $message');
    });
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_scrollController.hasClients) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
        );
      }
    });
  }

  void _onQueueEvent(QueueEvent event) {
    switch (event) {
      case QueueItemAdded():
        _addLog('πŸ“₯ Item added to queue');
        break;
      case QueueItemRemoved():
        _addLog('πŸ“€ Item removed from queue');
        break;
      case QueueItemFailed():
        _addLog('❌ Queue item failed');
        break;
      default:
        _addLog('πŸ“‹ Queue event: ${event.runtimeType}');
    }
  }

  void _onMetricsEvent(MetricsEvent event) {
    switch (event) {
      case RequestCompletedEvent(:final metrics):
        _addLog(
            'πŸ“Š Request completed in ${metrics.totalDuration.inMilliseconds}ms - ${metrics.success ? "SUCCESS" : "FAILED"}');
        break;
      case CacheHitEvent():
        _addLog('🎯 Cache hit');
        break;
      case CacheMissEvent():
        _addLog('❌ Cache miss');
        break;
      default:
        _addLog('πŸ“ˆ Metrics event: ${event.runtimeType}');
    }
  }

  void _onConnectivityEvent(ConnectivityInfo info) {
    _addLog('🌐 Connectivity: ${info.status} (${info.quality})');
  }

  Future<void> _testBasicGet() async {
    _addLog('πŸš€ Testing Basic GET Request...');

    final response = await client.get<Map<String, dynamic>>(
      'https://jsonplaceholder.typicode.com/posts/1',
      headers: {
        'User-Agent': 'SmartDio Flutter App/1.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      transformer: (data) => data as Map<String, dynamic>,
    );

    response.fold(
      (success) => _addLog(
          'βœ… GET Success: ${success.data['title']?.toString().substring(0, 30)}...'),
      (error) =>
          _addLog('❌ GET Error: ${error.error.toString().substring(0, 50)}...'),
    );
  }

  Future<void> _testBasicPost() async {
    _addLog('πŸš€ Testing Basic POST Request...');

    final response = await client.post<Map<String, dynamic>>(
      'https://jsonplaceholder.typicode.com/posts',
      headers: {
        'User-Agent': 'SmartDio Flutter App/1.0',
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: {
        'title': 'SmartDio Test Post',
        'body': 'Testing POST functionality',
        'userId': 1,
      },
      transformer: (data) => data as Map<String, dynamic>,
    );

    response.fold(
      (success) =>
          _addLog('βœ… POST Success: Created with ID ${success.data['id']}'),
      (error) => _addLog(
          '❌ POST Error: ${error.error.toString().substring(0, 50)}...'),
    );
  }

  Future<void> _testRetryMechanism() async {
    _addLog('πŸš€ Testing Retry Mechanism (will fail)...');

    final response = await client.get<String>(
      'https://httpstat.us/500', // This endpoint returns 500 status
      headers: {
        'User-Agent': 'SmartDio Flutter App/1.0',
        'Accept': 'application/json',
      },
      config: const RequestConfig(
        retryPolicy: RetryPolicy.exponentialBackoff(
          maxAttempts: 3,
          initialDelay: Duration(milliseconds: 200),
        ),
      ),
      transformer: (data) => data.toString(),
    );

    response.fold(
      (success) => _addLog('βœ… Retry Success (unexpected)'),
      (error) => _addLog('❌ Retry Failed after ${error.retryCount} attempts'),
    );
  }

  Future<void> _testCaching() async {
    _addLog('πŸš€ Testing Persistent Cache Functionality...');

    const testUrl = 'https://jsonplaceholder.typicode.com/posts/2';
    const headers = {
      'User-Agent': 'SmartDio Flutter App/1.0',
      'Accept': 'application/json',
    };

    // First request - should miss cache
    _addLog('πŸ“‘ Making first request (expect cache MISS)...');
    final response1 = await client.get<Map<String, dynamic>>(
      testUrl,
      headers: headers,
      config: const RequestConfig(
        cachePolicy: CachePolicy.networkFirst(ttl: Duration(minutes: 10)),
      ),
      transformer: (data) => data as Map<String, dynamic>,
    );

    response1.fold(
      (success) {
        _addLog('βœ… First request SUCCESS');
        _addLog('πŸ“Š From cache: ${success.isFromCache}');
        _addLog(
            'πŸ“„ Title: ${success.data['title']?.toString().substring(0, 20)}...');
      },
      (error) => _addLog('❌ First request failed: ${error.error}'),
    );

    // Small delay to show they're separate requests
    await Future.delayed(const Duration(milliseconds: 500));

    // Second request - should hit cache
    _addLog('πŸ“‘ Making second request (expect cache HIT)...');
    final response2 = await client.get<Map<String, dynamic>>(
      testUrl,
      headers: headers,
      config: const RequestConfig(
        cachePolicy: CachePolicy.networkFirst(ttl: Duration(minutes: 10)),
      ),
      transformer: (data) => data as Map<String, dynamic>,
    );

    response2.fold(
      (success) {
        _addLog('βœ… Second request SUCCESS');
        _addLog('πŸ“Š From cache: ${success.isFromCache}');
        _addLog(
            '⚑ Cache is ${success.isFromCache ? "WORKING" : "NOT WORKING"}!');
      },
      (error) => _addLog('❌ Second request failed: ${error.error}'),
    );

    // Show cache stats
    final stats = await cacheStore.getStats();
    _addLog(
        'πŸ’Ύ Cache entries: ${stats['validEntries']}/${stats['totalEntries']}');
  }

  Future<void> _testOfflineQueue() async {
    _addLog('πŸš€ Testing Offline Queue...');

    // Enable offline mode
    client.connectivity.setManualOfflineMode(true);
    _addLog('πŸ“΄ Offline mode enabled');

    final response = await client.post<Map<String, dynamic>>(
      'https://jsonplaceholder.typicode.com/posts',
      body: {
        'title': 'Offline Post',
        'body': 'This should be queued',
        'userId': 1,
      },
      transformer: (data) => data as Map<String, dynamic>,
    );

    _addLog('πŸ“‹ Queue size: ${client.queue.length}');

    response.fold(
      (success) => _addLog('βœ… Request processed'),
      (error) => _addLog('πŸ“₯ Request queued: ${error.error}'),
    );

    // Re-enable online mode
    client.connectivity.setManualOfflineMode(false);
    _addLog('🌐 Online mode restored');
  }

  Future<void> _testDeduplication() async {
    _addLog('πŸš€ Testing Request Deduplication...');

    // Send two identical requests quickly
    final futures = [
      client.get<Map<String, dynamic>>(
        'https://jsonplaceholder.typicode.com/posts/3',
        transformer: (data) => data as Map<String, dynamic>,
      ),
      client.get<Map<String, dynamic>>(
        'https://jsonplaceholder.typicode.com/posts/3',
        transformer: (data) => data as Map<String, dynamic>,
      ),
    ];

    final responses = await Future.wait(futures);
    _addLog('πŸ”„ Sent 2 identical requests');
    _addLog('πŸ“Š Response 1 - From cache: ${responses[0].isFromCache}');
    _addLog('πŸ“Š Response 2 - From cache: ${responses[1].isFromCache}');
  }

  Future<void> _testTypesSafety() async {
    _addLog('πŸš€ Testing Type Safety...');

    final response = await client.get<User>(
      'https://jsonplaceholder.typicode.com/users/1',
      transformer: (data) => User.fromJson(data as Map<String, dynamic>),
    );

    response.fold(
      (success) => _addLog(
          'βœ… Type Safe Success: ${success.data.name} (${success.data.email})'),
      (error) => _addLog('❌ Type Safe Error: ${error.error}'),
    );
  }

  void _showMetrics() async {
    final cacheMetrics = client.metrics.getCacheMetrics();
    final queueMetrics = client.metrics.getQueueMetrics(client.queue.length);
    final successRate = client.metrics.getSuccessRate();
    final avgTime = client.metrics.getAverageResponseTime();
    final cacheStats = await cacheStore.getStats();

    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('πŸ“Š Performance Metrics'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Performance Metrics
              const Text('πŸ“Š Performance Metrics',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              const SizedBox(height: 8),
              Text(
                  'Cache Hit Rate: ${(cacheMetrics.hitRate * 100).toStringAsFixed(1)}%'),
              Text('Cache Hits: ${cacheMetrics.hitCount}'),
              Text('Cache Misses: ${cacheMetrics.missCount}'),
              Text(
                  'Overall Success Rate: ${(successRate * 100).toStringAsFixed(1)}%'),
              Text('Average Response Time: ${avgTime.inMilliseconds}ms'),

              const SizedBox(height: 16),

              // Persistent Cache Stats
              const Text('πŸ’Ύ Persistent Cache (Hive)',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              const SizedBox(height: 8),
              Text('Total Entries: ${cacheStats['totalEntries']}'),
              Text('Valid Entries: ${cacheStats['validEntries']}'),
              Text('Expired Entries: ${cacheStats['expiredEntries']}'),
              Text(
                  'Total Size: ${(cacheStats['totalSizeBytes'] / 1024).toStringAsFixed(1)} KB'),

              const SizedBox(height: 16),

              // Queue Metrics
              const Text('πŸ“‹ Queue Metrics',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              const SizedBox(height: 8),
              Text('Queue Size: ${queueMetrics.currentSize}'),
              Text('Queue Processed: ${queueMetrics.totalProcessed}'),
              Text(
                  'Queue Success Rate: ${(queueMetrics.successRate * 100).toStringAsFixed(1)}%'),

              const SizedBox(height: 16),

              // Connectivity
              const Text('🌐 Connectivity',
                  style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
              const SizedBox(height: 8),
              Text('Status: ${client.connectivity.currentStatus.status}'),
              Text('Quality: ${client.connectivity.currentStatus.quality}'),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () async {
              await cacheStore.clear();
              _addLog('πŸ—‘οΈ Persistent cache cleared');
              Navigator.of(context).pop();
            },
            child: const Text('Clear Cache'),
          ),
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('Close'),
          ),
        ],
      ),
    );
  }

  void _clearLogs() {
    setState(() {
      logs.clear();
    });
  }

  Future<void> _testQueueStorage() async {
    _addLog('πŸš€ Testing Queue Storage Examples...');
    _addLog('πŸ“‹ Running queue storage demonstrations:');
    
    try {
      // Test 1: Persistent Storage
      _addLog('πŸ“¦ Testing Persistent Storage...');
      await _testPersistentStorage();
      
      // Test 2: Memory Storage  
      _addLog('🧠 Testing Memory Storage...');
      await _testMemoryStorage();
      
      // Test 3: No Storage
      _addLog('🚫 Testing No Storage...');
      await _testNoStorage();
      
      _addLog('βœ… All Queue Storage tests completed!');
      _addLog('πŸ“Š Current main client queue status:');
      _addLog('  β€’ Storage type: ${client.config.queueStorageType}');
      _addLog('  β€’ Current queue length: ${client.queue.length}');
      _addLog('  β€’ Max queue size: ${client.config.maxQueueSize}');
      _addLog('  β€’ Max queue age: ${client.config.maxQueueAge}');
    } catch (e) {
      _addLog('❌ Queue Storage test failed: ${e.toString()}');
    }
  }

  Future<void> _testPersistentStorage() async {
    const config = SmartDioConfig(
      queueStorageType: QueueStorageType.persistent,
      maxQueueSize: 50,
      maxQueueAge: Duration(days: 3),
      enableRequestQueue: true,
    );

    final testClient = SmartDioClient(
      adapter: DioClientAdapter(dioInstance: dio.Dio()),
      config: config,
    );

    _addLog('  β€’ Queue storage: Persistent (Hive)');
    _addLog('  β€’ Max size: 50, Max age: 3 days');

    // Force offline mode to test queuing
    testClient.connectivity.setManualOfflineMode(true);
    
    try {
      await testClient.post<Map<String, dynamic>>(
        'https://jsonplaceholder.typicode.com/posts',
        body: {'title': 'Persistent Test', 'body': 'Survives app restart'},
        transformer: (data) => data as Map<String, dynamic>,
      );
    } catch (e) {
      _addLog('  βœ“ Request queued (will survive restart): ${testClient.queue.length} items');
    }

    testClient.connectivity.setManualOfflineMode(false);
    await testClient.dispose();
  }

  Future<void> _testMemoryStorage() async {
    const config = SmartDioConfig(
      queueStorageType: QueueStorageType.memory,
      maxQueueSize: 100,
      enableRequestQueue: true,
    );

    final testClient = SmartDioClient(
      adapter: DioClientAdapter(dioInstance: dio.Dio()),
      config: config,
    );

    _addLog('  β€’ Queue storage: Memory only');
    _addLog('  β€’ Max size: 100');

    testClient.connectivity.setManualOfflineMode(true);
    
    try {
      await testClient.post<Map<String, dynamic>>(
        'https://jsonplaceholder.typicode.com/posts',
        body: {'title': 'Memory Test', 'body': 'Lost on restart'},
        transformer: (data) => data as Map<String, dynamic>,
      );
    } catch (e) {
      _addLog('  βœ“ Request queued in memory: ${testClient.queue.length} items');
    }

    testClient.connectivity.setManualOfflineMode(false);
    await testClient.dispose();
  }

  Future<void> _testNoStorage() async {
    const config = SmartDioConfig(
      queueStorageType: QueueStorageType.none,
      enableRequestQueue: false,
    );

    final testClient = SmartDioClient(
      adapter: DioClientAdapter(dioInstance: dio.Dio()),
      config: config,
    );

    _addLog('  β€’ Queue storage: Disabled');

    testClient.connectivity.setManualOfflineMode(true);
    
    try {
      await testClient.post<Map<String, dynamic>>(
        'https://jsonplaceholder.typicode.com/posts',
        body: {'title': 'No Queue Test', 'body': 'Fails immediately'},
        transformer: (data) => data as Map<String, dynamic>,
      );
    } catch (e) {
      _addLog('  βœ— Request failed immediately (no queuing): ${testClient.queue.length} items');
    }

    testClient.connectivity.setManualOfflineMode(false);
    await testClient.dispose();
  }

  Future<void> _testEnhancedLogging() async {
    _addLog('πŸš€ Testing Enhanced Logging...');
    _addLog('πŸ“‹ Running enhanced logging demonstrations:');
    _addLog('πŸ’‘ Check debug console for detailed response logging');
    
    try {
      // Run the enhanced logging examples
      await logging_example.main();
      _addLog('βœ… Enhanced Logging examples completed!');
      _addLog('πŸ“ Key improvements demonstrated:');
      _addLog('  - Raw response data logging');
      _addLog('  - Transformed object data logging');
      _addLog('  - Custom object serialization');
      _addLog('  - Fallback for non-serializable objects');
      _addLog('πŸ” Check the debug console for detailed logs');
    } catch (e) {
      _addLog('❌ Enhanced Logging test failed: ${e.toString()}');
    }
  }

  void _toggleOfflineMode() {
    setState(() {
      isOfflineMode = !isOfflineMode;
    });
    client.connectivity.setManualOfflineMode(isOfflineMode);
    _addLog('πŸ“΄ Manual offline mode: ${isOfflineMode ? "ON" : "OFF"}');
  }

  @override
  void dispose() {
    client.dispose();
    _scrollController.dispose();

    // Dispose all HTTP clients
    httpClient.close();
    dioClient.close();
    httpPackageClient.close();
    chopperClient.dispose();

    // Close persistent cache
    cacheStore.close();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SmartDio - ${_getClientName(currentClientType)}'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
        actions: [
          IconButton(
            icon: const Icon(Icons.analytics),
            onPressed: _showMetrics,
            tooltip: 'Show Metrics',
          ),
          IconButton(
            icon: Icon(isOfflineMode ? Icons.wifi_off : Icons.wifi),
            onPressed: _toggleOfflineMode,
            tooltip: 'Toggle Offline Mode',
          ),
        ],
      ),
      body: Column(
        children: [
          // Client Toggle Section
          Container(
            padding: const EdgeInsets.all(16.0),
            color: Colors.grey[50],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'HTTP Client Selection:',
                  style: TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 12),
                Row(
                  children: [
                    Expanded(
                      child: _buildClientToggle(
                        'dart:io\nHttpClient',
                        ClientType.httpClient,
                        Colors.indigo,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _buildClientToggle(
                        'Dio',
                        ClientType.dio,
                        Colors.blue,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _buildClientToggle(
                        'http\npackage',
                        ClientType.httpPackage,
                        Colors.green,
                      ),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: _buildClientToggle(
                        'Chopper',
                        ClientType.chopper,
                        Colors.purple,
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),

          // Buttons Section
          Expanded(
            flex: 2,
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: GridView.count(
                crossAxisCount: 2,
                crossAxisSpacing: 10,
                mainAxisSpacing: 10,
                childAspectRatio: 2.5,
                children: [
                  _buildTestButton(
                    'πŸš€ Basic GET',
                    Colors.green,
                    _testBasicGet,
                  ),
                  _buildTestButton(
                    'πŸ“ Basic POST',
                    Colors.blue,
                    _testBasicPost,
                  ),
                  _buildTestButton(
                    'πŸ”„ Test Retry',
                    Colors.orange,
                    _testRetryMechanism,
                  ),
                  _buildTestButton(
                    'πŸ’Ύ Test Cache',
                    Colors.purple,
                    _testCaching,
                  ),
                  _buildTestButton(
                    'πŸ“΄ Offline Queue',
                    Colors.red,
                    _testOfflineQueue,
                  ),
                  _buildTestButton(
                    'πŸ”„ Deduplication',
                    Colors.teal,
                    _testDeduplication,
                  ),
                  _buildTestButton(
                    '🎯 Type Safety',
                    Colors.indigo,
                    _testTypesSafety,
                  ),
                  _buildTestButton(
                    'πŸ“¦ Queue Storage',
                    Colors.deepPurple,
                    _testQueueStorage,
                  ),
                  _buildTestButton(
                    'πŸ“‹ Enhanced Logging',
                    Colors.amber,
                    _testEnhancedLogging,
                  ),
                  _buildTestButton(
                    'πŸ—‘οΈ Clear Logs',
                    Colors.grey,
                    _clearLogs,
                  ),
                ],
              ),
            ),
          ),

          // Status Bar
          Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            color: Colors.grey[100],
            child: Row(
              children: [
                Icon(
                  isOfflineMode ? Icons.wifi_off : Icons.wifi,
                  color: isOfflineMode ? Colors.red : Colors.green,
                  size: 20,
                ),
                const SizedBox(width: 8),
                Text(
                  'Queue: ${client.queue.length}',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
                const Spacer(),
                Text(
                  'Logs: ${logs.length}',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
              ],
            ),
          ),

          // Logs Section
          Expanded(
            flex: 3,
            child: Container(
              decoration: BoxDecoration(
                color: Colors.black87,
                border: Border.all(color: Colors.grey),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                    padding: const EdgeInsets.all(8),
                    color: Colors.grey[800],
                    child: const Row(
                      children: [
                        Icon(Icons.terminal, color: Colors.white, size: 16),
                        SizedBox(width: 8),
                        Text(
                          'Live Logs',
                          style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
                  ),
                  Expanded(
                    child: ListView.builder(
                      controller: _scrollController,
                      itemCount: logs.length,
                      itemBuilder: (context, index) {
                        final log = logs[index];
                        return Container(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 8,
                            vertical: 2,
                          ),
                          child: Text(
                            log,
                            style: TextStyle(
                              color: log.contains('❌')
                                  ? Colors.red
                                  : log.contains('βœ…')
                                      ? Colors.green
                                      : log.contains('πŸ“Š')
                                          ? Colors.blue
                                          : log.contains('πŸš€')
                                              ? Colors.yellow
                                              : Colors.white,
                              fontSize: 12,
                              fontFamily: 'monospace',
                            ),
                          ),
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildClientToggle(String text, ClientType clientType, Color color) {
    final isSelected = currentClientType == clientType;
    return ElevatedButton(
      onPressed: () => _switchClient(clientType),
      style: ElevatedButton.styleFrom(
        backgroundColor: isSelected ? color : Colors.grey[300],
        foregroundColor: isSelected ? Colors.white : Colors.black87,
        elevation: isSelected ? 4 : 1,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
          side: BorderSide(
            color: isSelected ? color : Colors.grey[400]!,
            width: isSelected ? 2 : 1,
          ),
        ),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 12.0),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(
              isSelected
                  ? Icons.radio_button_checked
                  : Icons.radio_button_unchecked,
              size: 20,
              color: isSelected ? Colors.white : Colors.grey[600],
            ),
            const SizedBox(height: 4),
            Text(
              text,
              style: TextStyle(
                fontSize: 11,
                fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              ),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildTestButton(String text, Color color, VoidCallback onPressed) {
    return ElevatedButton(
      onPressed: onPressed,
      style: ElevatedButton.styleFrom(
        backgroundColor: color,
        foregroundColor: Colors.white,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
      ),
      child: Text(
        text,
        style: const TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.bold,
        ),
        textAlign: TextAlign.center,
      ),
    );
  }
}

// User model for type safety testing
class User {
  final int id;
  final String name;
  final String email;
  final String username;

  User({
    required this.id,
    required this.name,
    required this.email,
    required this.username,
  });

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'] ?? 0,
      name: json['name'] ?? '',
      email: json['email'] ?? '',
      username: json['username'] ?? '',
    );
  }

  @override
  String toString() => 'User(id: $id, name: $name, email: $email)';
}
16
likes
130
points
99
downloads

Publisher

verified publisherrahulsha.com.np

Weekly Downloads

A transport-agnostic HTTP wrapper that enhances ANY HTTP client with offline caching, request queuing, retry mechanisms, and comprehensive logging.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

chopper, dio, flutter, hive, hive_flutter, http, meta, path_provider

More

Packages that depend on flutter_smartdio