Skip to content

Commit

Permalink
Fix Image's logical flow which disposes its image too early, causing …
Browse files Browse the repository at this point in the history
…errors such as "Cannot clone a disposed image" (#110131)
  • Loading branch information
fzyzcjy authored Aug 26, 2022
1 parent 0f17bfd commit 5be7e49
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/flutter/lib/src/widgets/image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1173,7 +1173,8 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
}

void _replaceImage({required ImageInfo? info}) {
_imageInfo?.dispose();
final ImageInfo? oldImageInfo = _imageInfo;
SchedulerBinding.instance.addPostFrameCallback((_) => oldImageInfo?.dispose());
_imageInfo = info;
}

Expand Down
47 changes: 47 additions & 0 deletions packages/flutter/test/widgets/image_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,53 @@ void main() {
imageCache.maximumSize = originalCacheSize;
});

testWidgets('Verify Image does not use disposed handles', (WidgetTester tester) async {
final ui.Image image100x100 = (await tester.runAsync(() async => createTestImage(width: 100, height: 100)))!;

final _TestImageProvider imageProvider1 = _TestImageProvider();
final _TestImageProvider imageProvider2 = _TestImageProvider();

final ValueNotifier<_TestImageProvider> imageListenable = ValueNotifier<_TestImageProvider>(imageProvider1);
final ValueNotifier<int> innerListenable = ValueNotifier<int>(0);

bool imageLoaded = false;

await tester.pumpWidget(ValueListenableBuilder<_TestImageProvider>(
valueListenable: imageListenable,
builder: (BuildContext context, _TestImageProvider image, Widget? child) => Image(
image: image,
frameBuilder: (BuildContext context, Widget child, int? frame, bool wasSynchronouslyLoaded) {
if (frame == 0) {
imageLoaded = true;
}
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) => ValueListenableBuilder<int>(
valueListenable: innerListenable,
builder: (BuildContext context, int value, Widget? valueListenableChild) => KeyedSubtree(
key: UniqueKey(),
child: child,
),
),
);
},
),
));

imageLoaded = false;
imageProvider1.complete(image10x10);
await tester.idle();
await tester.pump();
expect(imageLoaded, true);

imageLoaded = false;
imageListenable.value = imageProvider2;
innerListenable.value += 1;
imageProvider2.complete(image100x100);
await tester.idle();
await tester.pump();
expect(imageLoaded, true);
});

testWidgets('Verify Image resets its RenderImage when changing providers', (WidgetTester tester) async {
final GlobalKey key = GlobalKey();
final _TestImageProvider imageProvider1 = _TestImageProvider();
Expand Down

0 comments on commit 5be7e49

Please sign in to comment.