Fix crash when a MultiFrameImageStreamCompleter is disposed during frame decoding (#69219)
diff --git a/packages/flutter/lib/src/painting/image_stream.dart b/packages/flutter/lib/src/painting/image_stream.dart
index abffeee..1b6a8a5 100644
--- a/packages/flutter/lib/src/painting/image_stream.dart
+++ b/packages/flutter/lib/src/painting/image_stream.dart
@@ -920,6 +920,12 @@
return;
}
if (_codec!.frameCount == 1) {
+ // ImageStreamCompleter listeners removed while waiting for next frame to
+ // be decoded.
+ // There's no reason to emit the frame without active listeners.
+ if (!hasListeners) {
+ return;
+ }
// This is not an animated image, just return it and don't schedule more
// frames.
_emitFrame(ImageInfo(
diff --git a/packages/flutter/test/painting/image_stream_test.dart b/packages/flutter/test/painting/image_stream_test.dart
index 491b115..6b98462 100644
--- a/packages/flutter/test/painting/image_stream_test.dart
+++ b/packages/flutter/test/painting/image_stream_test.dart
@@ -130,6 +130,31 @@
expect(mockCodec.numFramesAsked, 1);
});
+ testWidgets('Decoding does not crash when disposed', (WidgetTester tester) async {
+ final Completer<Codec> completer = Completer<Codec>();
+ final MockCodec mockCodec = MockCodec();
+ mockCodec.frameCount = 1;
+ final ImageStreamCompleter imageStream = MultiFrameImageStreamCompleter(
+ codec: completer.future,
+ scale: 1.0,
+ );
+
+ completer.complete(mockCodec);
+ await tester.idle();
+ expect(mockCodec.numFramesAsked, 0);
+
+ final ImageListener listener = (ImageInfo image, bool synchronousCall) { };
+ final ImageStreamListener streamListener = ImageStreamListener(listener);
+ imageStream.addListener(streamListener);
+ await tester.idle();
+ expect(mockCodec.numFramesAsked, 1);
+
+ final FrameInfo frame = FakeFrameInfo(const Duration(milliseconds: 200), image20x10);
+ mockCodec.completeNextFrame(frame);
+ imageStream.removeListener(streamListener);
+ await tester.idle();
+ });
+
testWidgets('Chunk events of base ImageStreamCompleter are delivered', (WidgetTester tester) async {
final List<ImageChunkEvent> chunkEvents = <ImageChunkEvent>[];
final StreamController<ImageChunkEvent> streamController = StreamController<ImageChunkEvent>();