Skip to main content

Caching

Zuraffa v3 supports caching with a dual data source pattern. The --cache flag generates the full setup with configurable policies.

Overview

Caching in Zuraffa follows the dual DataSource pattern:

  • Remote DataSource: Fetches data from external sources (API, database)
  • Local DataSource: Stores data locally (Hive, SQLite, shared preferences)
  • Cached Repository: Coordinates between remote and local sources

Basic Caching Setup

1. Generate with Caching

zfa generate Product \
--methods=get,getList,create,update,delete \
--data \
--cache

This generates:

  • Remote DataSource (for API calls)
  • Local DataSource (for local storage)
  • Cached Repository (coordinates both)

2. Configure Cache Policy

Choose from three cache policies:

PolicyDescriptionUse Case
dailyCache expires at midnightDaily updated content
restartCache persists until app restartSession-based data
ttlCache expires after time periodTime-sensitive data
# Daily cache policy (default)
zfa generate Product --data --cache --cache-policy=daily

# TTL cache (expires after 2 hours)
zfa generate Product --data --cache --cache-policy=ttl --ttl=120

# Restart cache
zfa generate Product --data --cache --cache-policy=restart

3. Select Storage Backend

Currently supports Hive storage:

zfa generate Product --data --cache --cache-storage=hive

Generated Architecture

Local DataSource

// lib/src/data/data_sources/product/product_local_data_source.dart
import 'package:hive_ce/hive_ce.dart';
import '../../../domain/entities/product/product.dart';

class ProductLocalDataSource {
static const String boxName = 'ProductBox';

Future<Box<Product>> get _box async => Hive.box<Product>(boxName);

Future<Product?> get(String id) async {
final box = await _box;
return box.get(id);
}

Future<List<Product>> getList() async {
final box = await _box;
return box.values.toList();
}

Future<void> save(Product product) async {
final box = await _box;
await box.put(product.id, product);
}

Future<void> saveAll(List<Product> products) async {
final box = await _box;
await box.putAll(Map.fromEntries(
products.map((p) => MapEntry(p.id, p)),
));
}

Future<void> delete(String id) async {
final box = await _box;
await box.delete(id);
}
}

Remote DataSource

// lib/src/data/data_sources/product/product_remote_data_source.dart
import '../../../domain/entities/product/product.dart';

class ProductRemoteDataSource {
Future<Product> get(String id) async {
// TODO: Implement API call
throw UnimplementedError();
}

Future<List<Product>> getList() async {
// TODO: Implement API call
throw UnimplementedError();
}

Future<Product> create(Product product) async {
// TODO: Implement API call
throw UnimplementedError();
}

Future<Product> update(Product product) async {
// TODO: Implement API call
throw UnimplementedError();
}

Future<void> delete(String id) async {
// TODO: Implement API call
throw UnimplementedError();
}
}

Cached Repository

// lib/src/data/repositories/data_product_repository.dart
import 'package:zuraffa/zuraffa.dart';
import '../../domain/entities/product/product.dart';
import '../data_sources/product/product_remote_data_source.dart';
import '../data_sources/product/product_local_data_source.dart';
import '../../cache/product_cache.dart';

class DataProductRepository extends ProductRepository {
final ProductRemoteDataSource _remoteDataSource;
final ProductLocalDataSource _localDataSource;
final ProductCache _cache;

DataProductRepository(
this._remoteDataSource,
this._localDataSource,
this._cache,
);


Future<Product> get(String id) async {
// Check cache first
if (await _cache.isValid()) {
try {
final cached = await _localDataSource.get(id);
if (cached != null) return cached;
} catch (_) {}
}

// Fetch from remote and cache
final product = await _remoteDataSource.get(id);
await _localDataSource.save(product);
await _cache.updateTimestamp();
return product;
}


Future<List<Product>> getList() async {
// Check cache first
if (await _cache.isValid()) {
try {
return await _localDataSource.getList();
} catch (_) {}
}

// Fetch from remote and cache
final products = await _remoteDataSource.getList();
await _localDataSource.saveAll(products);
await _cache.updateTimestamp();
return products;
}

// ... other methods
}

Cache Policies

Daily Cache Policy

Expires at midnight (based on device timezone):

// lib/src/cache/daily_cache_policy.dart
import 'package:hive_ce/hive_ce.dart';

class DailyCachePolicy implements CachePolicy {
static const String boxName = 'CachePolicyBox';
static const String timestampKey = 'daily_timestamp';


Future<bool> isValid() async {
final box = Hive.box(timestampBoxName);
final timestamp = box.get(timestampKey) as int?;

if (timestamp == null) return false;

final lastUpdated = DateTime.fromMillisecondsSinceEpoch(timestamp);
final today = DateTime.now();

return lastUpdated.day == today.day &&
lastUpdated.month == today.month &&
lastUpdated.year == today.year;
}


Future<void> updateTimestamp() async {
final box = Hive.box(timestampBoxName);
await box.put(timestampKey, DateTime.now().millisecondsSinceEpoch);
}


Future<void> invalidate() async {
final box = Hive.box(timestampBoxName);
await box.delete(timestampKey);
}
}

TTL Cache Policy

Expires after specified minutes:

zfa generate Product --data --cache --cache-policy=ttl --ttl=60  # 1 hour

Restart Cache Policy

Persists until app restart:

zfa generate Product --data --cache --cache-policy=restart

ZFA Patterns and Caching

Entity-Based Pattern

Perfect for standard entity caching:

zfa generate Product \
--methods=get,getList,create,update,delete \
--data \
--cache \
--cache-policy=daily \
--cache-storage=hive

Single Repository Pattern

Caching works with custom UseCases too:

zfa generate ProcessCheckout \
--domain=checkout \
--repo=Checkout \
--params=CheckoutRequest \
--returns=OrderConfirmation \
--cache

Orchestrator Pattern

Each composed UseCase can have its own caching strategy.

Advanced Configuration

Custom TTL Values

# 30 minutes
zfa generate Product --cache --cache-policy=ttl --ttl=30

# 6 hours
zfa generate Product --cache --cache-policy=ttl --ttl=360

# 1 day (1440 minutes - this is the default)
zfa generate Product --cache --cache-policy=ttl --ttl=1440

Combining with Other Features

# Complete setup with caching, DI, and testing
zfa generate Product \
--methods=get,getList,create,update,delete \
--data \
--cache \
--cache-policy=daily \
--di \
--test

Using with Mock Data

# Development with mock data and caching
zfa generate Product \
--methods=get,getList \
--data \
--cache \
--mock \
--use-mock

Cache Initialization

Zuraffa automatically generates cache initialization code:

// lib/src/cache/index.dart
import 'package:hive_ce/hive_ce.dart';
import 'product_cache.dart';
import 'daily_cache_policy.dart';
import 'timestamp_cache.dart';

Future<void> initProductCaches() async {
// Register adapters
Hive.registerAdapter(ProductAdapter());

// Open boxes
await Hive.openBox<Product>('ProductBox');
await Hive.openBox<int>('CachePolicyBox');
await Hive.openBox<int>('TimestampBox');
}

Future<void> initAllCaches() async {
await initProductCaches();
// Add other cache initializations here
}

Manual Cache Management

Invalidating Cache

// In your UseCase or Repository
class RefreshProductUseCase extends UseCase<Product, String> {
final ProductRepository _repository;
final ProductCache _cache;

RefreshProductUseCase(this._repository, this._cache);


Future<Product> execute(String id, CancelToken? cancelToken) async {
// Invalidate cache to force fresh fetch
await _cache.invalidate();

// Fetch fresh data
return _repository.get(id);
}
}

Checking Cache Status

// Check if cache is valid before expensive operations
if (await productCache.isValid()) {
// Use cached data
final products = await localDataSource.getList();
} else {
// Fetch fresh data
final products = await remoteDataSource.getList();
}

Best Practices

1. Choose the Right Cache Policy

# Daily: For content that updates once per day
zfa generate NewsArticle --cache --cache-policy=daily

# TTL: For time-sensitive data (prices, weather)
zfa generate StockPrice --cache --cache-policy=ttl --ttl=5

# Restart: For session data
zfa generate UserSession --cache --cache-policy=restart

2. Combine with Offline Support

# Robust offline-first setup
zfa generate Product \
--methods=get,getList,create,update,delete \
--data \
--cache \
--cache-policy=daily \
--mock # For development

3. Use with Dependency Injection

# Complete setup with DI
zfa generate Product \
--data \
--cache \
--di

Migration from 1.x

Before (1.x)

# Caching was less sophisticated
zfa generate Product --cache

After (2.0.0)

# More granular control over cache policies and storage
zfa generate Product \
--data \
--cache \
--cache-policy=daily \
--cache-storage=hive

Troubleshooting

Hive Initialization

If getting Hive errors, ensure proper initialization in main():

void main() async {
WidgetsFlutterBinding.ensureInitialized();

// Initialize Hive
await Hive.initFlutter();
await initAllCaches(); // Generated cache initialization

runApp(MyApp());
}

Cache Not Updating

Make sure to call _cache.updateTimestamp() after successful remote fetches in your Repository.

Next Steps