Skip to content

📦 Caching & Offline Strategy Standards

Table of Contents

Core Standards

Cache Setup Rules

  1. Always use dio_cache_interceptor for HTTP caching
  2. Configure cache store with appropriate size limits
  3. Set cache policies based on data sensitivity
  4. Use cache keys for manual cache management
  5. Implement cache cleanup strategies
  6. Handle cache errors gracefully
dart
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:dio_cache_interceptor_mem_store/dio_cache_interceptor_mem_store.dart';

// Memory cache store configuration
final cacheStore = MemCacheStore(
  maxSize: 10485760, // 10MB cache size
  maxEntrySize: 1048576, // 1MB per entry
);

// Cache options
final cacheOptions = CacheOptions(
  store: cacheStore,
  policy: CachePolicy.request,
  hitCacheOnErrorExcept: [401, 403],
  maxStale: const Duration(hours: 2),
  priority: CachePriority.normal,
  keyBuilder: (request) => request.uri.toString(),
);

Dio Configuration Rules

  1. Add cache interceptor to Dio instance
  2. Configure cache options per request or globally
  3. Use appropriate cache policies for different endpoints
  4. Handle cache headers properly
  5. Implement cache invalidation when needed
dart
import 'package:dio/dio.dart';

final dio = Dio();

// Add cache interceptor
dio.interceptors.add(DioCacheInterceptor(options: cacheOptions));

// Per-request cache configuration
final response = await dio.get(
  '/api/users',
  options: CacheOptions(
    policy: CachePolicy.cacheFirst,
    maxStale: const Duration(hours: 1),
  ).toOptions(),
);

Cache Configuration

Cache Store Setup

  1. Use MemCacheStore for fast in-memory caching
  2. Configure appropriate cache size based on app needs
  3. Set cache expiration policies
  4. Implement cache cleanup mechanisms
  5. Handle cache storage errors
dart
// Configure memory cache store
final cacheStore = MemCacheStore(
  maxSize: 10485760, // 10MB total cache size
  maxEntrySize: 1048576, // 1MB per cache entry
);

// Cache options with store
final cacheOptions = CacheOptions(
  store: cacheStore,
  policy: CachePolicy.request,
  maxStale: const Duration(hours: 4),
  hitCacheOnErrorExcept: [401, 403, 500],
);

Cache Size Management

  1. Set maximum cache size based on available memory
  2. Implement LRU eviction for cache management
  3. Monitor cache usage and cleanup when needed
  4. Use appropriate cache priorities for different data types
dart
// Memory cache size configuration
final cacheStore = MemCacheStore(
  maxSize: 20971520, // 20MB total cache size
  maxEntrySize: 2097152, // 2MB per cache entry
);

final cacheOptions = CacheOptions(
  store: cacheStore,
  policy: CachePolicy.request,
  maxStale: const Duration(hours: 6),
  priority: CachePriority.high, // For critical data
  keyBuilder: (request) {
    // Custom cache key generation
    return '${request.method}_${request.uri.path}_${request.queryParameters}';
  },
);

Cache Policies

Policy Selection Rules

  1. Use CachePolicy.request for most API calls
  2. Use CachePolicy.cacheFirst for static data
  3. Use CachePolicy.networkFirst for real-time data
  4. Use CachePolicy.cacheOnly for offline-only data
  5. Use CachePolicy.noCache for sensitive data
dart
// Different cache policies for different data types
class CachePolicyConfig {
  // Static data - cache first
  static const staticData = CacheOptions(
    policy: CachePolicy.cacheFirst,
    maxStale: Duration(days: 30),
  );

  // User data - network first
  static const userData = CacheOptions(
    policy: CachePolicy.networkFirst,
    maxStale: Duration(hours: 1),
  );

  // Real-time data - no cache
  static const realTimeData = CacheOptions(
    policy: CachePolicy.noCache,
  );

  // Offline data - cache only
  static const offlineData = CacheOptions(
    policy: CachePolicy.cacheOnly,
  );
}

Cache Duration Rules

  1. Use short durations for frequently changing data
  2. Use long durations for static content
  3. Use medium durations for user-specific data
  4. Consider data freshness requirements
  5. Implement cache refresh strategies
dart
// Memory cache duration configuration
final cacheOptions = CacheOptions(
  store: MemCacheStore(maxSize: 10485760), // 10MB cache
  policy: CachePolicy.request,
  maxStale: const Duration(hours: 2), // Data can be 2 hours old
  hitCacheOnErrorExcept: [401, 403], // Use cache on network errors
  priority: CachePriority.normal,
);

Implementation Guidelines

Repository Integration

  1. Configure cache options in repository methods
  2. Use appropriate cache policies per endpoint
  3. Handle cache hits and misses properly
  4. Implement cache invalidation when data changes
  5. Monitor cache performance
dart
class UserRepository {
  final Dio _dio;

  UserRepository(this._dio);

  Future<List<User>> getUsers() async {
    try {
      final response = await _dio.get(
        '/api/users',
        options: CacheOptions(
          policy: CachePolicy.networkFirst,
          maxStale: const Duration(minutes: 30),
          keyBuilder: (request) => 'users_list',
        ).toOptions(),
      );

      return (response.data as List)
          .map((json) => User.fromJson(json))
          .toList();
    } catch (e) {
      // Handle cache miss or network error
      throw Exception('Failed to fetch users: $e');
    }
  }

  Future<void> createUser(User user) async {
    await _dio.post('/api/users', data: user.toJson());
    
    // Invalidate users cache after creation
    await _dio.delete(
      '/api/users',
      options: CacheOptions(
        policy: CachePolicy.noCache,
        keyBuilder: (request) => 'users_list',
      ).toOptions(),
    );
  }
}

Cache Key Management

  1. Use descriptive cache keys for easy identification
  2. Include request parameters in cache keys
  3. Use consistent key patterns across the app
  4. Implement key versioning for cache invalidation
  5. Handle key conflicts properly
dart
// Cache key builder
String buildCacheKey(String endpoint, Map<String, dynamic>? params) {
  final key = StringBuffer(endpoint);
  
  if (params != null && params.isNotEmpty) {
    key.write('_');
    key.write(params.entries
        .map((e) => '${e.key}=${e.value}')
        .join('&'));
  }
  
  return key.toString();
}

// Usage in repository
final cacheKey = buildCacheKey('/api/users', {'page': 1, 'limit': 10});

Offline Handling

Offline Detection Rules

  1. Check network connectivity before making requests
  2. Use cached data when offline
  3. Show appropriate UI states for offline mode
  4. Implement offline indicators for users
  5. Handle offline data synchronization
dart
import 'package:connectivity_plus/connectivity_plus.dart';

class NetworkService {
  final Connectivity _connectivity = Connectivity();

  Future<bool> isOnline() async {
    final result = await _connectivity.checkConnectivity();
    return result != ConnectivityResult.none;
  }

  Future<T> getDataWithOfflineSupport<T>(
    String endpoint,
    T Function(Map<String, dynamic>) fromJson,
    CacheOptions cacheOptions,
  ) async {
    final isConnected = await isOnline();
    
    if (!isConnected) {
      // Force cache-only policy when offline
      cacheOptions = cacheOptions.copyWith(
        policy: CachePolicy.cacheOnly,
      );
    }

    final response = await _dio.get(
      endpoint,
      options: cacheOptions.toOptions(),
    );

    return fromJson(response.data);
  }
}

Offline Data Management

  1. Cache critical data for offline access
  2. Implement offline-first strategies for key features
  3. Use background sync when connection is restored
  4. Handle offline data conflicts properly
  5. Provide offline indicators in UI
dart
class OfflineDataManager {
  final Dio _dio;
  final CacheOptions _offlineCacheOptions;

  OfflineDataManager(this._dio) 
      : _offlineCacheOptions = CacheOptions(
          store: MemCacheStore(maxSize: 5242880), // 5MB for offline data
          policy: CachePolicy.cacheOnly,
          maxStale: const Duration(hours: 24), // Keep data for 24 hours
        );

  Future<List<T>> getOfflineData<T>(
    String endpoint,
    T Function(Map<String, dynamic>) fromJson,
  ) async {
    try {
      final response = await _dio.get(
        endpoint,
        options: _offlineCacheOptions.toOptions(),
      );

      return (response.data as List)
          .map((json) => fromJson(json))
          .toList();
    } catch (e) {
      // Return empty list if no cached data
      return [];
    }
  }
}

Common Violations

DO NOT Violate These Rules

  1. Don't cache sensitive data without encryption
  2. Don't use excessive cache durations for real-time data
  3. Don't ignore cache invalidation after data updates
  4. Don't use CachePolicy.noCache for static data
  5. Don't forget to handle cache errors gracefully
  6. Don't use inconsistent cache keys across the app
  7. Don't ignore offline scenarios in cache configuration
  8. Don't set excessive memory cache sizes that could cause OOM
  9. Don't use cache for authentication tokens without expiration
  10. Don't forget that memory cache is lost on app restart

ALWAYS Follow These Rules

  1. Use appropriate cache policies based on data type
  2. Set reasonable cache durations for different data
  3. Implement cache invalidation after data mutations
  4. Use descriptive cache keys for easy management
  5. Handle offline scenarios with proper cache configuration
  6. Monitor cache performance and usage
  7. Use cache priorities to manage storage efficiently
  8. Set appropriate memory limits for cache size
  9. Test cache behavior in offline/online scenarios
  10. Remember that memory cache is temporary and app-scoped