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:
- Fork the repository on GitHub.
- Create a new branch for your changes.
- Make your changes in the branch.
- 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.