hope_cache 0.1.6
hope_cache: ^0.1.6 copied to clipboard
Simple and fast caching for Dart/Flutter with TTL support, multiple eviction strategies, and pluggable storage.
// ignore_for_file: avoid_print
import 'package:hope_cache/cache_manager.dart';
import 'package:hope_cache/cache_store.dart';
import 'package:flutter/material.dart';
import 'package:hope_cache/hope_builder.dart';
import 'package:hope_cache/hope_client.dart';
import 'package:hope_cache/hope_options.dart';
import 'package:hope_cache/mutation_builder.dart';
//basic usage
Future<void> basicUsage() async {
final cache = await CacheManager.create(
maxSize: 1024 * 1024,
defaultTTL: Duration(minutes: 5),
evictionPolicy: EvictionPolicy.lru,
);
// Store individual entries
await cache.set('user_123', {'name': 'Alice', 'age': 30});
await cache.set('user_456', {'name': 'Bob', 'age': 25});
// Store multiple entries at once
await cache.setMany({
'product_1': {'name': 'Phone', 'price': 999},
'product_2': {'name': 'Laptop', 'price': 1999},
});
// Read a single entry if present
final user = await cache.getIfPresent('user_123');
print(user);
// Read multiple entries at once
final products = await cache.getMany([
'product_1',
'product_2',
'product_3', // ignored if not present
]);
print(products);
// Read with fallback fetcher
final settings = await cache.get('app_settings', () async {
return {'theme': 'dark', 'language': 'en'};
});
print(settings);
// Invalidate a single entry
await cache.invalidate('user_456');
// Invalidate entries by prefix
await cache.invalidatePattern('product_');
// Clear all cached data
await cache.clear();
}
// Implement CacheStore interface;
class MyCustomStore implements CacheStore {
final Map<String, String> _storage = {};
@override
Future<void> write(String key, String value) async {
_storage[key] = value;
}
@override
Future<String?> read(String key) async => _storage[key];
@override
Future<void> delete(String key) async => _storage.remove(key);
@override
Future<void> clear() async => _storage.clear();
@override
Future<List<String>> getAllKeys() async => _storage.keys.toList();
@override
Future<int> getKeySize(String key) async => _storage[key]?.length ?? 0;
@override
Future<int> getTotalSize() async {
int total = 0;
for (final value in _storage.values) {
total += value.length;
}
return total;
}
@override
Future<Map<String, String>> getAllEntries() async => Map.from(_storage);
}
//eviction polices
Future<void> evictionPolicies() async {
// LRU — evicts the least recently accessed entry
final lruCache = await CacheManager.create(
maxSize: 200,
defaultTTL: Duration(hours: 1),
evictionPolicy: EvictionPolicy.lru,
);
// LFU — evicts the least frequently accessed entry
final lfuCache = await CacheManager.create(
maxSize: 200,
defaultTTL: Duration(hours: 1),
evictionPolicy: EvictionPolicy.lfu,
);
// FIFO — evicts the oldest inserted entry
final fifoCache = await CacheManager.create(
maxSize: 200,
defaultTTL: Duration(hours: 1),
evictionPolicy: EvictionPolicy.fifo,
);
// Add entries until eviction is triggered
await lruCache.set('a', 'A');
await lruCache.set('b', 'B');
await lruCache.set('c', 'C');
// Access one entry to affect eviction order
await lruCache.getIfPresent('a');
// Adding another entry may evict a different key
await lruCache.set('d', 'D');
print(lruCache.getStats());
}
//map keys
Future<void> mapKey() async {
final cache = await CacheManager.create(
maxSize: 1024 * 1024,
defaultTTL: Duration(minutes: 5),
evictionPolicy: EvictionPolicy.lru,
);
// Store using a Map key
await cache.set(
{'resource': 'product', 'id': '123'},
{'name': 'Laptop'},
);
// Same key, different order
final product = await cache.getIfPresent(
{'id': '123', 'resource': 'product'},
);
print(product); // {name: Laptop}
}
//query data
// ─── 1. Setup ───────────────────────────────────────────────────────────────
void queryData() async {
WidgetsFlutterBinding.ensureInitialized();
await HopeClient.init(
maxSize: 1024 * 1024,
defaultTTL: Duration(minutes: 5),
evictionPolicy: EvictionPolicy.lru,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(home: UsersScreen());
}
}
// ─── 2. Basic Query ──────────────────────────────────────────────────────────
class UsersScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HopeBuilder<List<String>>(
queryKey: ['users'],
fetcher: (_) async => ['Alice', 'Bob', 'Charlie'],
builder: (context, state) {
if (state.isLoading) return CircularProgressIndicator();
if (state.isError) return Text(state.error.toString());
return ListView(
children: state.data!.map((u) => ListTile(title: Text(u))).toList(),
);
},
);
}
}
// ─── 3. Dependent Query ──────────────────────────────────────────────────────
class UserPostsScreen extends StatelessWidget {
final String? userId;
const UserPostsScreen({this.userId});
@override
Widget build(BuildContext context) {
return HopeBuilder<List<String>>(
queryKey: ['posts', userId],
fetcher: (_) async => ['Post 1', 'Post 2'],
options: HopeOptions(enabled: userId != null),
builder: (context, state) {
if (state.isIdle) return Text('No user selected');
if (state.isLoading) return CircularProgressIndicator();
if (state.isError) return Text(state.error.toString());
return ListView(
children: state.data!.map((p) => ListTile(title: Text(p))).toList(),
);
},
);
}
}
// ─── 4. Infinite Scroll ──────────────────────────────────────────────────────
class ProductsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HopeBuilder<List<String>>(
queryKey: ['products'],
fetcher: (pageParam) async {
final cursor = pageParam ?? 0;
return List.generate(10, (i) => 'Product ${cursor + i}');
},
getNextPageParam: (lastPage) =>
lastPage.length == 10 ? lastPage.length : null,
builder: (context, state) {
if (state.isLoading) return CircularProgressIndicator();
if (state.isError) return Text(state.error.toString());
final items = state.pages!.expand((p) => p).toList();
return ListView.builder(
itemCount: items.length + (state.hasMore == true ? 1 : 0),
itemBuilder: (context, index) {
if (index == items.length) {
state.fetchMore();
return CircularProgressIndicator();
}
return ListTile(title: Text(items[index]));
},
);
},
);
}
}
// ─── 5. Mutation ─────────────────────────────────────────────────────────────
class AddUserScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HopeMutationBuilder<String, String>(
mutation: (name) async => 'Created: $name',
onSuccess: (data) {
HopeClient.instance.invalidate(['users']);
},
onError: (error) => debugPrint(error.toString()),
builder: (context, state, mutate) {
if (state.isError) return Text(state.error.toString());
return ElevatedButton(
onPressed: state.isMutating ? null : () => mutate('Hope'),
child: state.isMutating
? CircularProgressIndicator()
: Text('Add User'),
);
},
);
}
}
// ─── 6. Prefetch ─────────────────────────────────────────────────────────────
class HomeScreen extends StatelessWidget {
void _onHoverUserTab() {
HopeClient.instance.prefetch(
queryKey: ['users'],
fetcher: () async => ['Alice', 'Bob', 'Charlie'],
);
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _onHoverUserTab,
child: Text('Prefetch Users'),
);
}
}
// ─── 7. Optimistic Update ────────────────────────────────────────────────────
class OptimisticScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HopeBuilder<List<String>>(
queryKey: ['users'],
fetcher: (_) async => ['Alice', 'Bob'],
builder: (context, state) {
if (state.isLoading) return CircularProgressIndicator();
return HopeMutationBuilder<String, String>(
mutation: (name) async => name,
onSuccess: (_) => HopeClient.instance.invalidate(['users']),
builder: (context, mutationState, mutate) {
return ElevatedButton(
onPressed: () async {
// Update UI instantly before mutation completes
await HopeClient.instance.prefetch(
queryKey: ['users'],
fetcher: () async => [...state.data!, 'Hope'],
);
await mutate('Hope');
},
child: Text('Add Hope Optimistically'),
);
},
);
},
);
}
}
//ttl configuration
Future<void> ttlConfiguration() async {
final cache = await CacheManager.create(
maxSize: 1024 * 1024,
defaultTTL: Duration(minutes: 5), // Default for all keys
evictionPolicy: EvictionPolicy.lru,
);
// Uses default TTL
await cache.set('session', {'token': 'abc'});
// Custom TTL
await cache.set(
'temp',
{'value': 'expires soon'},
ttl: Duration(seconds: 10),
);
await Future.delayed(Duration(seconds: 11));
print('Temp (expired): ${await cache.getIfPresent('temp')}');
print('Session (valid): ${await cache.getIfPresent('session')}');
}