dio_cache_plus 2.0.3 copy "dio_cache_plus: ^2.0.3" to clipboard
dio_cache_plus: ^2.0.3 copied to clipboard

Enhanced Dio caching interceptor with per-request control, conditional caching, and expiration.

Dio Cache Plus #

Dio Cache Plus is an advanced caching interceptor for Dio β€” offering per-request control, smart conditional caching, absolute expiry support, dynamic function-based durations, and efficient request deduplication.


🧱 Acknowledgments #

Originally inspired by flutter-network-sanitizer by Ahmed Elkholy.
Completely reworked and expanded with a modern API, expiry-based caching, dynamic function support, version-safe Hive storage, and advanced request rules.


✨ Features #

βœ… Conditional Caching β€” cache only the requests you want.
βœ… Per-Request Control β€” customize caching for each call.
βœ… Smart Expiry β€” use either a relative Duration or an absolute DateTime expiry.
βœ… Dynamic Functions β€” use functions to calculate durations/expiry at runtime.
βœ… Request Deduplication β€” identical concurrent requests share the same network response.
βœ… Persistent Cross-Platform Storage β€” powered by Hive.
βœ… Automatic Migration β€” safe schema updates without crashes.
βœ… Global or Local Cache Management β€” total flexibility.
βœ… Accurate Time-Based Expiry β€” expiry calculations happen at storage time for precision.


πŸ“¦ Installation #

Add to your pubspec.yaml:

dependencies:
  dio_cache_plus: ^2.0.0

Then, install it by running:

flutter pub get

πŸš€ Quick Start #

Simply add DioCachePlusInterceptor to your Dio instance.

import 'package:dio/dio.dart';
import 'package:dio_cache_plus/dio_cache_plus.dart';

void main() async {
  final dio = Dio();

  dio.interceptors.add(
    DioCachePlusInterceptor(
      cacheAll: false,
      commonCacheDuration: const Duration(minutes: 5),
      isErrorResponse: (response) => response.statusCode != 200,

      // βœ… Conditional rules from constructor
      conditionalRules: [
        // Cache GET user API responses for 10 minutes
        ConditionalCacheRule.duration(
          condition: (request) =>
              request.method == 'GET' && request.url.contains('/users'),
          duration: const Duration(minutes: 10),
        ),

        // Cache GET market data until the market closes (e.g., 3:30 PM)
        ConditionalCacheRule.expiry(
          condition: (request) =>
              request.method == 'GET' && request.url.contains('/market'),
          expiry: DateTime.now().copyWith(hour: 15, minute: 30),
        ),

        // Dynamic caching based on time of day
        ConditionalCacheRule.durationFn(
          condition: (request) =>
              request.method == 'GET' && request.url.contains('/news'),
          durationFn: () {
            final hour = DateTime.now().hour;
            // Cache longer during off-peak hours
            return hour >= 22 || hour < 6 
                ? const Duration(hours: 4)
                : const Duration(minutes: 30);
          },
        ),
      ],
    ),
  );

  // Not cached
  await dio.get('/api/news');

  // Cached for 5 minutes (default duration)
  final cached = await dio.get(
    '/api/users',
    options: Options().setCaching(enableCache: true),
  );
}

πŸ› οΈ Usage Examples #

Per-Request Control #

Use type-safe methods for different caching strategies:

// Cache GET requests with static duration
final response1 = await dio.get(
  '/api/big_data',
  options: Options().setCachingWithDuration(
    enableCache: true,
    duration: const Duration(hours: 2),
  ),
);

// Cache with dynamic duration function
final response2 = await dio.get(
  '/api/dynamic_data',
  options: Options().setCachingWithDurationFn(
    enableCache: true,
    durationFn: () {
      final hour = DateTime.now().hour;
      return hour >= 22 || hour < 6 
          ? const Duration(hours: 4)
          : const Duration(minutes: 30);
    },
  ),
);

// Cache with static expiry
final response3 = await dio.get(
  '/api/market_data',
  options: Options().setCachingWithExpiry(
    enableCache: true,
    expiry: DateTime.now().copyWith(hour: 16, minute: 0),
  ),
);

// Cache with dynamic expiry function
final response4 = await dio.get(
  '/api/reports',
  options: Options().setCachingWithExpiryFn(
    enableCache: true,
    expiryFn: () => DateTime.now().add(const Duration(days: 1)),
  ),
);

// Simple caching (uses global default duration)
final response5 = await dio.get(
  '/api/data',
  options: Options().setCaching(enableCache: true),
);

// Disable cache even if global cacheAll=true
final noCache = await dio.get(
  '/api/live_feed',
  options: Options().setCaching(enableCache: false),
);

Dynamic Duration Functions #

Calculate cache durations dynamically at runtime:

// Weekend-aware caching
final weekendResponse = await dio.get(
  '/api/weekly-report',
  options: Options().setCachingWithDurationFn(
    enableCache: true,
    durationFn: () {
      final isWeekend = DateTime.now().weekday >= 6; // Saturday or Sunday
      return isWeekend 
          ? const Duration(days: 3)  // Longer cache on weekends
          : const Duration(hours: 12); // Shorter cache on weekdays
    },
  ),
);

// User-based caching strategies
final userResponse = await dio.get(
  '/api/user/profile',
  options: Options().setCachingWithDurationFn(
    enableCache: true,
    durationFn: () {
      final userType = authService.currentUser?.type;
      switch (userType) {
        case UserType.premium:
          return const Duration(hours: 4);
        case UserType.standard:
          return const Duration(hours: 1);
        case UserType.guest:
        default:
          return const Duration(minutes: 15);
      }
    },
  ),
);

Absolute Expiry with Dynamic Functions #

Calculate expiry times dynamically based on business logic:

// Cache until next market close (dynamic calculation)
final marketResponse = await dio.get(
  '/api/market-data',
  options: Options().setCachingWithExpiryFn(
    enableCache: true,
    expiryFn: () {
      final now = DateTime.now();
      // If it's after 4 PM, cache until next day 4 PM
      if (now.hour >= 16) {
        return DateTime(now.year, now.month, now.day + 1, 16, 0);
      } else {
        return DateTime(now.year, now.month, now.day, 16, 0);
      }
    },
  ),
);

// Cache until top of next hour
final topOfHourResponse = await dio.get(
  '/api/hourly-data',
  options: Options().setCachingWithExpiryFn(
    enableCache: true,
    expiryFn: () {
      final now = DateTime.now();
      return DateTime(now.year, now.month, now.day, now.hour + 1);
    },
  ),
);

Force Refresh #

Bypass the cache to get fresh data from the server:

// This request will ignore any cached data and hit the network
final freshResponse = await dio.get(
  '/api/users/123',
  options: Options().setCachingWithDuration(
    enableCache: true,
    duration: const Duration(minutes: 30),
    invalidateCache: true, // Forces network request
  ),
);

Request Deduplication #

Concurrent identical GET requests are automatically merged:

final futures = List.generate(
  10,
  (_) => dio.get('/api/trending'),
);

final responses = await Future.wait(futures);
// Only 1 actual network request was made 🎯

πŸ”§ Runtime Rule Management #

Add or remove conditional caching rules dynamically at runtime:

// Add rule with static duration
DioCachePlusInterceptor.addConditionalCaching(
  'user_cache',
  ConditionalCacheRule.duration(
    condition: (request) => 
        request.method == 'GET' && request.url.contains('/users'),
    duration: const Duration(minutes: 30),
  ),
);

// Add rule with dynamic duration function
DioCachePlusInterceptor.addConditionalCaching(
  'news_cache',
  ConditionalCacheRule.durationFn(
    condition: (request) => 
        request.method == 'GET' && request.url.contains('/news'),
    durationFn: () {
      final hour = DateTime.now().hour;
      return hour >= 22 || hour < 6 
          ? const Duration(hours: 4)   // Longer cache at night
          : const Duration(minutes: 30); // Shorter cache during day
    },
  ),
);

// Add rule with static expiry
DioCachePlusInterceptor.addConditionalCaching(
  'market_cache',
  ConditionalCacheRule.expiry(
    condition: (request) => 
        request.method == 'GET' && request.url.contains('/market'),
    expiry: DateTime.now().copyWith(hour: 16, minute: 0), // Until 4 PM today
  ),
);

// Add rule with dynamic expiry function  
DioCachePlusInterceptor.addConditionalCaching(
  'report_cache',
  ConditionalCacheRule.expiryFn(
    condition: (request) => 
        request.method == 'GET' && request.url.contains('/reports'),
    expiryFn: () => DateTime.now().add(const Duration(days: 1)), // Until same time tomorrow
  ),
);

// Add rule without specific timing (uses global default)
DioCachePlusInterceptor.addConditionalCaching(
  'api_cache',
  ConditionalCacheRule.conditionalOnly(
    condition: (request) => 
        request.method == 'GET' && request.url.contains('/api'),
  ),
);

// Remove a rule
DioCachePlusInterceptor.removeConditionalCaching('user_cache');

// Remove cached data based on condition
DioCachePlusInterceptor.removeConditionalCachingData(
  (request) => request.method == 'GET' && request.url.contains('/users'),
);

// Clear all cached data (but keep rules)
await DioCachePlusInterceptor.clearAll();

βš™οΈ How It Works #

Caching Flow #

  1. Request Interception: When a request is made, a unique key is generated based on method, URL, and parameters.
  2. Cache Check: The interceptor checks if caching is enabled via global config, per-request options, or matching conditional rules.
  3. Expiry Validation: If cached data exists, its timestamp is checked against the configured duration/expiry.
  4. Network Fallback: If no valid cache exists, the request proceeds to the network.
  5. Storage: Successful responses are stored with precise expiry calculation at storage time.

Smart Expiry Calculation #

Unlike other caching solutions, Dio Cache Plus calculates expiry durations at the moment of storage, not when the request is configured. This ensures:

  • Market closing times expire exactly at the specified time
  • Time-sensitive data respects absolute deadlines
  • Dynamic functions are executed at cache time for fresh values
  • No timing drift between request configuration and actual caching

Function Execution Precedence #

When multiple duration/expiry options are provided, they are evaluated in this order:

  1. expiryFn - Dynamic expiry function
  2. expiry - Static expiry DateTime
  3. durationFn - Dynamic duration function
  4. duration - Static duration
  5. Conditional rule functions
  6. Global default duration

Deduplication #

  • The interceptor tracks all outgoing network requests
  • Identical concurrent requests are queued instead of creating new network calls
  • All queued requests receive the same result when the original completes

Cache Invalidation #

  • Force Refresh: invalidateCache: true removes existing cache before making the network request
  • Automatic Expiration: Entries expire based on their configured duration or absolute expiry
  • Conditional Data Removal: removeConditionalCachingData() removes cached data matching specific patterns
  • Global Clear: clearAll() wipes the entire cache

πŸ“š API Reference #

DioCachePlusInterceptor #

The main interceptor class.

Constructor Parameter Type Required Description
cacheAll bool Yes When true, caches all requests by default. When false, requires explicit opt-in via setCaching()
commonCacheDuration Duration Yes Default cache duration when no specific duration is provided
isErrorResponse bool Function(Response) Yes Predicate to determine if a response represents an error (prevents caching of errors)
conditionalRules List<ConditionalCacheRule>? No List of conditional caching rules applied at interceptor creation

Static Methods:

  • addConditionalCaching(String key, ConditionalCacheRule rule) - Adds a conditional caching rule
  • removeConditionalCaching(String key) - Removes a conditional rule
  • removeConditionalCachingData(RequestMatcher condition) - Removes cached data matching the condition
  • clearAll() - Clears all cached data

CacheOptionsExtension #

Per-request cache control via Options extension methods:

Method Parameters Description
setCachingWithDuration enableCache, duration, overrideConditionalCache, invalidateCache Caching with static duration
setCachingWithDurationFn enableCache, durationFn, overrideConditionalCache, invalidateCache Caching with dynamic duration function
setCachingWithExpiry enableCache, expiry, overrideConditionalCache, invalidateCache Caching with static expiry DateTime
setCachingWithExpiryFn enableCache, expiryFn, overrideConditionalCache, invalidateCache Caching with dynamic expiry function
setCaching enableCache, overrideConditionalCache, invalidateCache Simple caching (uses global default)

ConditionalCacheRule Factory Constructors #

Define conditional caching rules with precise expiry control:

Constructor Parameters Description
duration condition, duration Rule with static duration
durationFn condition, durationFn Rule with dynamic duration function
expiry condition, expiry Rule with static expiry DateTime
expiryFn condition, expiryFn Rule with dynamic expiry function
conditionalOnly condition Rule without timing (uses global default)

RequestDetails Object #

The RequestDetails object passed to condition functions contains:

Property Type Description
method String HTTP method (GET, POST, etc.)
url String Full request URL
queryParameters Map<String, dynamic> Request query parameters

🎯 Advanced Usage #

Mixed Caching Strategies #

// Combine static and dynamic caching rules
dio.interceptors.add(
  DioCachePlusInterceptor(
    cacheAll: false,
    commonCacheDuration: const Duration(minutes: 10),
    isErrorResponse: (r) => r.statusCode != 200,
    conditionalRules: [
      // GET User data with dynamic duration
      ConditionalCacheRule.durationFn(
        condition: (request) =>
          request.method == 'GET' && request.url.contains('/users'),
        durationFn: () {
          final isWeekend = DateTime.now().weekday >= 6;
          return isWeekend 
              ? const Duration(hours: 6)
              : const Duration(hours: 2);
        },
      ),
      // GET News articles cached until end of day
      ConditionalCacheRule.expiryFn(
        condition: (request) =>
          request.method == 'GET' && request.url.contains('/news'),
        expiryFn: () {
          final now = DateTime.now();
          return DateTime(now.year, now.month, now.day, 23, 59, 59);
        },
      ),
    ],
  ),
);

Custom Cache Key Generation #

Override the default cache key generation for specific requests:

final response = await dio.get(
  '/api/data',
  options: Options(extra: {
    'generatedRequestKey': 'custom_key_123', // Custom cache key
  }).setCachingWithDuration(
    enableCache: true,
    duration: const Duration(hours: 2),
  ),
);

🌍 Platform Support #

Platform Status
Android βœ…
iOS βœ…
Web βœ…
Windows βœ…
macOS βœ…
Linux βœ…

πŸ† Performance Benefits #

  • Reduced Network Calls: Caching and deduplication cut redundant requests by up to 90%
  • Faster Response Times: Local cache serves data instantaneously
  • Lower Bandwidth Usage: Optimized for slow networks and metered connections
  • Precise Expiry Control: Time-sensitive data respects exact deadlines
  • Dynamic Optimization: Cache behavior adapts to runtime conditions
  • Server Load Reduction: Protect backend services from unnecessary traffic
  • Improved User Experience: Apps feel faster and more responsive

πŸ”§ Troubleshooting #

Cache Not Working? #

  • Ensure enableCache: true is set in setCaching() or cacheAll: true in interceptor
  • Check that your isErrorResponse function correctly identifies successful responses
  • Verify conditional rule conditions match your requests (remember to check method == 'GET')

Expiry Not Respected? #

  • Use expiry or expiryFn instead of duration for absolute time boundaries
  • Expiry is calculated at storage time, so it's always accurate
  • Check that your DateTime includes timezone information if needed

Function Errors? #

  • Wrap your durationFn/expiryFn in try-catch blocks if they might throw exceptions
  • Functions are executed at cache time, not configuration time
  • Ensure functions don't have side effects that could cause issues

Memory Issues? #

  • The interceptor automatically cleans up completed requests
  • Hive provides efficient disk-based storage
  • Use clearAll() periodically if needed

🀝 Contributing #

We welcome contributions! Please see our Contributing Guide for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

πŸ“œ License #

This project is licensed under the MIT License - see the LICENSE file for details.


Dio Cache Plus - Smart, dynamic, precise caching for Flutter apps. ⚑

13
likes
160
points
399
downloads

Publisher

verified publisherbalu.dev

Weekly Downloads

Enhanced Dio caching interceptor with per-request control, conditional caching, and expiration.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#dio #caching #http #interceptor #performance

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

crypto, dio, flutter, hive, hive_flutter

More

Packages that depend on dio_cache_plus