NoSQL Cache Manager for Flutter

A powerful, feature-rich cache manager for Flutter applications that provides efficient local data storage with automatic expiration, batch operations, and comprehensive cache management. Built on top of Sembast NoSQL database for reliable persistent storage.

✨ Features

  • πŸš€ High Performance: Efficient data caching with automatic expiration
  • πŸ“¦ Batch Operations: Set, get, and delete multiple cache entries at once
  • ⏰ Flexible Expiration: Customizable cache expiry times with automatic cleanup
  • πŸ” Cache Analytics: Get cache size, check key existence, and list all keys
  • 🧹 Smart Cleanup: Automatic removal of expired entries and manual cleanup methods
  • πŸ’Ύ Persistent Storage: Uses Sembast NoSQL database for reliable data persistence
  • πŸ›‘οΈ Type Safe: Supports any JSON-serializable data types
  • πŸ“± Cross Platform: Works on Android, iOS, and other Flutter-supported platforms
  • πŸ”„ Transaction Support: Atomic batch operations for data consistency
  • 🎯 Easy Integration: Simple API with comprehensive documentation

πŸ“¦ Installation

Add this to your package's pubspec.yaml file:

dependencies:
  nosql_cache_manager: ^0.0.1

Then run:

flutter pub get

πŸš€ Quick Start

import 'package:nosql_cache_manager/cache_manager.dart';

void main() async {
  final cacheManager = CacheManager();
  
  // Initialize the cache manager
  await cacheManager.init();
  
  // Store some data
  await cacheManager.setCache('user_data', {
    'name': 'John Doe',
    'email': 'john@example.com',
    'preferences': ['dark_mode', 'notifications']
  });
  
  // Retrieve the data
  final userData = await cacheManager.getCache('user_data');
  print('User: ${userData['name']}');
  
  // Clean up
  await cacheManager.close();
}

πŸ“– Comprehensive Usage Guide

Basic Operations

Initialize Cache Manager

final cacheManager = CacheManager();
await cacheManager.init(); // Must be called before using cache operations

Store Data with Custom Expiration

// Cache for 1 hour (default)
await cacheManager.setCache('api_response', responseData);

// Cache for 15 minutes
await cacheManager.setCache('temp_token', 'abc123', 
  duration: Duration(minutes: 15));

// Cache for 7 days
await cacheManager.setCache('user_preferences', preferences,
  duration: Duration(days: 7));

Retrieve Cached Data

final data = await cacheManager.getCache('api_response');
if (data != null) {
  // Data exists and hasn't expired
  print('Cached data: $data');
} else {
  // Data doesn't exist or has expired
  print('Cache miss - need to fetch fresh data');
}

Check if Key Exists

if (await cacheManager.hasKey('user_data')) {
  print('User data is cached');
}

Batch Operations

Set Multiple Entries

await cacheManager.setBatch({
  'user_1': {'name': 'Alice', 'role': 'admin'},
  'user_2': {'name': 'Bob', 'role': 'user'},
  'user_3': {'name': 'Charlie', 'role': 'moderator'},
}, duration: Duration(hours: 2));

Get Multiple Entries

final results = await cacheManager.getBatch(['user_1', 'user_2', 'user_4']);
print('User 1: ${results['user_1']}'); // User data or null
print('User 2: ${results['user_2']}'); // User data or null  
print('User 4: ${results['user_4']}'); // null (doesn't exist)

Delete Multiple Entries

await cacheManager.deleteBatch(['temp_data', 'old_cache', 'expired_token']);

Cache Management

Get Cache Statistics

final size = await cacheManager.getCacheSize();
final keys = await cacheManager.getAllKeys();

print('Cache contains $size entries');
print('Keys: $keys');

Clean Expired Entries

final cleanedCount = await cacheManager.cleanExpired();
print('Cleaned up $cleanedCount expired entries');

Clear All Cache

await cacheManager.clearAllCache();

Advanced Usage Patterns

API Response Caching

class ApiService {
  final CacheManager _cache = CacheManager();
  
  Future<Map<String, dynamic>> getUserProfile(String userId) async {
    final cacheKey = 'user_profile_$userId';
    
    // Try to get from cache first
    var userData = await _cache.getCache(cacheKey);
    if (userData != null) {
      return userData;
    }
    
    // Fetch from API if not cached
    userData = await _fetchUserFromApi(userId);
    
    // Cache for 30 minutes
    await _cache.setCache(cacheKey, userData, 
      duration: Duration(minutes: 30));
    
    return userData;
  }
}

Offline-First Data Strategy

class DataRepository {
  final CacheManager _cache = CacheManager();
  
  Future<List<dynamic>> getArticles() async {
    const cacheKey = 'articles';
    
    // Always try cache first
    var articles = await _cache.getCache(cacheKey);
    if (articles != null) {
      // Optionally refresh in background
      _refreshArticlesInBackground();
      return articles;
    }
    
    // Fetch fresh data if cache miss
    articles = await _fetchArticlesFromApi();
    await _cache.setCache(cacheKey, articles, 
      duration: Duration(hours: 1));
    
    return articles;
  }
  
  void _refreshArticlesInBackground() async {
    try {
      final freshArticles = await _fetchArticlesFromApi();
      await _cache.setCache('articles', freshArticles, 
        duration: Duration(hours: 1));
    } catch (e) {
      // Silently fail - user still has cached data
    }
  }
}

Cache Warming Strategy

class CacheWarmer {
  final CacheManager _cache = CacheManager();
  
  Future<void> warmEssentialData() async {
    // Pre-load frequently accessed data
    final futures = [
      _warmUserPreferences(),
      _warmAppConfig(),
      _warmFrequentlyAccessedData(),
    ];
    
    await Future.wait(futures);
  }
  
  Future<void> _warmUserPreferences() async {
    final prefs = await _fetchUserPreferences();
    await _cache.setCache('user_preferences', prefs,
      duration: Duration(days: 1));
  }
}

πŸ”§ Best Practices

1. Initialize Once

// Good: Initialize once in your app
class CacheService {
  static final CacheManager _instance = CacheManager();
  static bool _initialized = false;
  
  static Future<CacheManager> getInstance() async {
    if (!_initialized) {
      await _instance.init();
      _initialized = true;
    }
    return _instance;
  }
}

2. Use Appropriate Expiration Times

// Short-lived data (API tokens, temporary states)
await cache.setCache('auth_token', token, duration: Duration(minutes: 15));

// Medium-lived data (user profiles, app settings)
await cache.setCache('user_profile', profile, duration: Duration(hours: 6));

// Long-lived data (app configuration, rarely changing data)
await cache.setCache('app_config', config, duration: Duration(days: 7));

3. Handle Cache Misses Gracefully

Future<UserData> getUserData(String userId) async {
  try {
    var userData = await cache.getCache('user_$userId');
    if (userData != null) return UserData.fromJson(userData);
    
    // Fallback to API
    userData = await apiService.fetchUser(userId);
    await cache.setCache('user_$userId', userData.toJson());
    return userData;
  } catch (e) {
    // Handle errors appropriately
    throw CacheException('Failed to retrieve user data: $e');
  }
}

4. Regular Cleanup

// Schedule periodic cleanup
Timer.periodic(Duration(hours: 6), (timer) async {
  final cleaned = await cacheManager.cleanExpired();
  print('Cleaned $cleaned expired entries');
});

πŸ§ͺ Testing

The package includes comprehensive tests. Run them with:

flutter test

Example test setup:

void main() {
  group('Cache Manager Tests', () {
    late CacheManager cacheManager;
    
    setUp(() {
      cacheManager = CacheManager();
    });
    
    tearDown(() async {
      await cacheManager.clearAllCache();
      await cacheManager.close();
    });
    
    test('should cache and retrieve data', () async {
      await cacheManager.setCache('test_key', 'test_value');
      final result = await cacheManager.getCache('test_key');
      expect(result, equals('test_value'));
    });
  });
}

Additional information

Contributing

We openly welcome contributions from the developer community. Whether it's improving the code, fixing bugs, or enhancing documentation, your input is valuable. To contribute, please follow these steps:

  1. Fork the repository on GitHub.
  2. Create a new branch for your changes.
  3. Make your changes in the branch.
  4. Submit a pull request with a clear description of the improvements.

Before contributing, please read our Contributing Guidelines for more details on the process and coding standards.

Filing Issues

Encountering an issue or have a suggestion? We encourage you to file issues through our GitHub Issues Page. When filing an issue, please provide the following:

  • A clear and descriptive title.
  • A detailed description of the issue or suggestion.
  • Steps to reproduce the issue (if applicable).
  • Any relevant code snippets or error messages.

Support and Response

Our team is committed to providing support for the package users. While we strive to respond to issues and pull requests promptly, please allow a reasonable amount of time for a response. For immediate support or specific queries, you can also reach out to us via official support channel.