Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions flutter_cache_manager/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [3.4.6] - 2026-09-28

* Allows failed resized-image downloads to be retried by clearing completed resize streams ([#439](https://github.com/Baseflow/flutter_cache_manager/issues/439)).

## [3.4.5] - 2026-09-18

* Prevents image resize decoding from completing more than once and propagates decoding errors ([#400](https://github.com/Baseflow/flutter_cache_manager/issues/400)).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,53 @@ mixin ImageCacheManager on BaseCacheManager {
}
var runningResize = _runningResizes[resizedKey];
if (runningResize == null) {
runningResize = _fetchedResizedFile(
url,
key,
runningResize = _trackResizeStream(
resizedKey,
headers,
withProgress,
maxWidth: maxWidth,
maxHeight: maxHeight,
).asBroadcastStream();
_fetchedResizedFile(
url,
key,
resizedKey,
headers,
withProgress,
maxWidth: maxWidth,
maxHeight: maxHeight,
),
);
_runningResizes[resizedKey] = runningResize;
}
yield* runningResize;
_runningResizes.remove(resizedKey);
}

final Map<String, Stream<FileResponse>> _runningResizes = {};

Stream<FileResponse> _trackResizeStream(
String resizedKey,
Stream<FileResponse> stream,
) {
// Clear the in-flight entry on both success and error so failures can be
// retried by a later request.
late final Stream<FileResponse> trackedStream;
trackedStream = stream
.transform(
StreamTransformer<FileResponse, FileResponse>.fromHandlers(
handleError: (Object error, StackTrace stackTrace, sink) {
if (identical(_runningResizes[resizedKey], trackedStream)) {
_runningResizes.remove(resizedKey);
}
sink.addError(error, stackTrace);
},
handleDone: (sink) {
if (identical(_runningResizes[resizedKey], trackedStream)) {
_runningResizes.remove(resizedKey);
}
sink.close();
},
),
)
.asBroadcastStream();
return trackedStream;
}

Future<FileInfo> _resizeImageFile(
FileInfo originalFile,
String key,
Expand Down
2 changes: 1 addition & 1 deletion flutter_cache_manager/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_cache_manager
description: Generic cache manager for flutter. Saves web files on the storages of the device and saves the cache info using sqflite.
version: 3.4.5
version: 3.4.6
homepage: https://github.com/Baseflow/flutter_cache_manager
topics:
- cache
Expand Down
47 changes: 47 additions & 0 deletions flutter_cache_manager/test/image_cache_manager_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

import 'cache_manager_test.dart';
import 'helpers/config_extensions.dart';
import 'helpers/mock_file_fetcher_response.dart';
import 'helpers/test_configuration.dart';

const fileName = 'test.jpg';
Expand Down Expand Up @@ -71,6 +73,51 @@ void main() {
);
});
group('Test resized image caching', () {
test('Failed resize download can be retried', () async {
final config = createTestConfig();
final cacheManager = TestCacheManager(config);
var downloadCount = 0;
when(
config.mockFileService.get(fileUrl, headers: anyNamed('headers')),
).thenAnswer((_) async {
downloadCount++;
if (downloadCount == 1) {
return MockFileFetcherResponse(
const Stream.empty(),
0,
null,
'',
404,
DateTime.now(),
);
}

final imageBytes = await getExampleImage();
return MockFileFetcherResponse(
Stream.value(imageBytes),
imageBytes.length,
'testv1',
'.png',
200,
DateTime.now().add(const Duration(days: 1)),
);
});

await expectLater(
cacheManager.getImageFile(fileUrl, maxHeight: 100).toList(),
throwsA(isA<HttpExceptionWithStatus>()),
);

final results = await cacheManager
.getImageFile(fileUrl, maxHeight: 100)
.timeout(const Duration(seconds: 1))
.toList();

expect(downloadCount, 2);
expect(results, hasLength(1));
expect(results.single, isA<FileInfo>());
});

test('Resized image should be fetched from cache', () async {
var config = await setupConfig(cacheKey: 'resized_w100_h80_$fileUrl');
var cacheManager = TestCacheManager(config);
Expand Down