CacheManager: does not wait for data to be persisted. Potential consistency issue
🐛 Bug Report
due to data consistency issues with JsonCacheInfoRepository, reported in https://github.com/Baseflow/flutter_cache_manager/issues/491, i started checking if i could use 'CacheObjectProvider', it looks fine, but the same issue can occur because cachemanager doesn't wait for data to be persisted because it is missing an "await" on line 232. is this intentional? why?
Expected behavior
Reproduction steps
Configuration
Version: 1.x
Platform:
- [x] :iphone: iOS
- [x] :robot: Android
Hello,
I've encountered the exact same issue in my project. The problem occurs because the store.putFile(newCacheObject) call inside WebHelper is not awaited, meaning the CacheObject might not have a valid ID immediately after getFileStream completes. This causes subsequent operations (like removeFile) that rely on cacheData.id to fail.
As a workaround, I implemented this extension method to wait for the cache data to be fully stored and have a valid ID before proceeding:
CacheManagerExt.waitCacheDataId
extension CacheManagerExt on CacheManager {
/// Ensures that a cache entry associated with the given [key] has a valid ID.
///
/// This method addresses a limitation in the standard WebHelper implementation
/// (see: https://github.com/Baseflow/flutter_cache_manager/blob/54904e499a06d0364a2b3f4ca9789e5f829f1879/flutter_cache_manager/lib/src/web/web_helper.dart#L142-L146),
/// where `store.putFile(newCacheObject)` is called without `await`. As a result,
/// it completes asynchronously after the `getFileStream` finishes. This means the
/// `CacheObject` might not have a valid ID immediately after `getFileStream` completes,
/// causing subsequent operations like `removeFile` to fail, as they rely on a valid
/// `CacheObject.id`.
///
/// For example:
///
/// ```dart
/// // This approach is unreliable:
/// await cacheManager.getFileStream(url, key: key.value, withProgress: true);
/// await cacheManager.removeFile(key);
///
/// // Explanation:
/// // The `removeFile` call might fail because the `CacheObject` stored during
/// // `getFileStream` may not yet have a valid ID. This happens because
/// // `store.putFile` completes asynchronously.
///
/// // Correct approach with waitCacheDataId:
/// await cacheManager.getFileStream(url, key: key.value, withProgress: true);
/// await cacheManager.waitForCacheDataId(key.value); // Ensures the CacheObject has a valid ID
/// await cacheManager.removeFile(key);
///
/// // Explanation:
/// // By using waitCacheDataId, we ensure that the `CacheObject` is fully initialized
/// // with a valid ID before calling `removeFile`. This avoids issues caused by
/// // the asynchronous nature of `store.putFile`.
/// ```
///
/// By waiting until the cache data has a valid ID, this method ensures that
/// subsequent operations depending on the `CacheObject.id` will function correctly.
Future<void> waitCacheDataId(String key, {Duration? timeout}) async {
var future = Future<void>(
() async {
while (true) {
final cacheData = await store.retrieveCacheData(key);
// Break if data is not found or has a valid ID
if (cacheData == null || cacheData.id != null) {
break;
}
// Prevent tight loop
await Future.delayed(const Duration(milliseconds: 100));
}
},
);
if (timeout != null) {
future = future.timeout(timeout);
}
return future;
}
}
This has resolved the issue for me. Hopefully, this can help others facing the same problem until a permanent fix is implemented in the package itself.