getAll<T> method
Retrieves multiple values associated with the given keys
.
Returns a map where the keys are the original keys and the values are the retrieved values. If a key is not found in the cache or its value is expired, it will not be included in the returned map.
If refreshCallbacks
is provided and an item is stale according to its policy,
the callback for that key will be used to refresh the data based on the refresh strategy.
Throws a CacheException if there is an error retrieving the data.
Implementation
Future<Map<String, T>> getAll<T>(
List<String> keys, {
Map<String, Future<T> Function()>? refreshCallbacks,
CachePolicy? policy,
}) async {
try {
if (keys.isEmpty) {
return {};
}
// Check for empty keys
for (final key in keys) {
if (key.isEmpty) {
throw ArgumentError('Keys cannot be empty');
}
}
final result = <String, T>{};
final cacheItems = await _cacheAdapter.getAll(keys);
final keysToDelete = <String>[];
final effectivePolicy = policy ?? CachePolicy.defaultPolicy;
// For large datasets without refresh callbacks, process decompression in isolate
if (keys.length > 50 &&
(refreshCallbacks == null || refreshCallbacks.isEmpty) &&
cacheItems.isNotEmpty) {
_log.fine(
'Processing ${cacheItems.length} items in isolate for getAll');
// Create a list of compressed items that need decompression
final compressedItems = <String, String>{};
for (final entry in cacheItems.entries) {
if (entry.value.isCompressed && entry.value.value is String) {
compressedItems[entry.key] = entry.value.value as String;
}
}
// If there are compressed items, decompress them in an isolate
if (compressedItems.isNotEmpty) {
final decompressedItems =
await IsolateRunner.run<Map<String, String>, Map<String, String>>(
function: (items) {
final compression = Compression();
final results = <String, String>{};
for (final entry in items.entries) {
try {
results[entry.key] =
compression.decompressString(entry.value);
} catch (e) {
// If decompression fails, keep the original value
results[entry.key] = entry.value;
}
}
return results;
},
message: compressedItems,
);
// Update the cache items with decompressed values
for (final entry in decompressedItems.entries) {
final cacheItem = cacheItems[entry.key]!;
result[entry.key] = entry.value as T;
// Update sliding expiry if needed
if (cacheItem.slidingExpiry != null) {
final updatedCacheItem = cacheItem.updateExpiry();
await _cacheAdapter.put(entry.key, updatedCacheItem);
} else {
// Update access metadata
final updatedCacheItem = cacheItem.updateExpiry();
if (updatedCacheItem != cacheItem) {
await _cacheAdapter.put(entry.key, updatedCacheItem);
}
}
// Record cache hit in analytics
_analytics.recordHit(entry.key);
}
// Process non-compressed items
for (final entry in cacheItems.entries) {
if (!entry.value.isCompressed && !result.containsKey(entry.key)) {
if (entry.value.isExpired) {
// Record cache miss in analytics
_analytics.recordMiss(entry.key);
keysToDelete.add(entry.key);
continue;
}
result[entry.key] = entry.value.value as T;
// Update sliding expiry if needed
if (entry.value.slidingExpiry != null) {
final updatedCacheItem = entry.value.updateExpiry();
await _cacheAdapter.put(entry.key, updatedCacheItem);
} else {
// Update access metadata
final updatedCacheItem = entry.value.updateExpiry();
if (updatedCacheItem != entry.value) {
await _cacheAdapter.put(entry.key, updatedCacheItem);
}
}
// Record cache hit in analytics
_analytics.recordHit(entry.key);
}
}
// Delete expired items in batch
if (keysToDelete.isNotEmpty) {
await _cacheAdapter.deleteAll(keysToDelete);
}
return result;
}
}
// Process cache hits normally for smaller datasets or when refresh callbacks are provided
for (final entry in cacheItems.entries) {
final key = entry.key;
final cacheItem = entry.value;
if (cacheItem.isExpired) {
// Record cache miss in analytics
_analytics.recordMiss(key);
keysToDelete.add(key);
// If a refresh callback is provided for this key, use it to get fresh data
if (refreshCallbacks != null && refreshCallbacks.containsKey(key)) {
final freshValue = await refreshCallbacks[key]!();
await put(key, freshValue, policy: policy);
result[key] = freshValue;
}
continue;
}
// Check if the item is stale and needs refreshing
if (refreshCallbacks != null &&
refreshCallbacks.containsKey(key) &&
effectivePolicy.staleTime != null &&
cacheItem.isStale(effectivePolicy.staleTime!)) {
// Handle different refresh strategies
switch (effectivePolicy.refreshStrategy) {
case RefreshStrategy.backgroundRefresh:
// Update in the background without blocking
_refreshInBackground(
key, refreshCallbacks[key]!, effectivePolicy);
break;
case RefreshStrategy.immediateRefresh:
// Refresh immediately and return the fresh value
final freshValue = await refreshCallbacks[key]!();
await put(key, freshValue, policy: effectivePolicy);
result[key] = freshValue;
continue; // Skip the rest of the loop for this item
case RefreshStrategy.never:
// Do nothing, just use the cached value
break;
}
}
// Decompress the value if it's compressed
T? resultValue;
if (cacheItem.isCompressed &&
_compression != null &&
cacheItem.value is String) {
try {
final decompressedValue =
_compression.decompressString(cacheItem.value as String);
resultValue = decompressedValue as T?;
_log.fine('Decompressed value for key $key');
} catch (e) {
_log.warning('Failed to decompress value for key $key: $e');
resultValue = cacheItem.value as T?;
}
} else {
resultValue = cacheItem.value as T?;
}
// Update sliding expiry if needed
if (cacheItem.slidingExpiry != null) {
final updatedCacheItem = cacheItem.updateExpiry();
await _cacheAdapter.put(key, updatedCacheItem);
// Record cache hit in analytics
_analytics.recordHit(key);
result[key] = resultValue as T;
} else {
// Update access metadata
final updatedCacheItem = cacheItem.updateExpiry();
if (updatedCacheItem != cacheItem) {
await _cacheAdapter.put(key, updatedCacheItem);
}
// Record cache hit in analytics
_analytics.recordHit(key);
result[key] = resultValue as T;
}
}
// Process cache misses with refresh callbacks
if (refreshCallbacks != null) {
final missingKeys = keys.where((key) =>
!result.containsKey(key) &&
!keysToDelete.contains(key) &&
refreshCallbacks.containsKey(key));
for (final key in missingKeys) {
// Record cache miss in analytics
_analytics.recordMiss(key);
final freshValue = await refreshCallbacks[key]!();
await put(key, freshValue, policy: policy);
result[key] = freshValue;
}
}
// Delete expired items in batch
if (keysToDelete.isNotEmpty) {
await _cacheAdapter.deleteAll(keysToDelete);
}
return result;
} on HiveError catch (e) {
_log.severe('Failed to get data from cache (HiveError): $e');
throw CacheException('Failed to get data from cache: ${e.message}');
} catch (e) {
_log.severe('Failed to get data from cache (Unknown Error): $e');
throw CacheException('Failed to get data from cache: $e');
}
}