diff --git a/packages/flutter/lib/src/painting/image_stream.dart b/packages/flutter/lib/src/painting/image_stream.dart index 88fac19b65..a9d8ec34ea 100644 --- a/packages/flutter/lib/src/painting/image_stream.dart +++ b/packages/flutter/lib/src/painting/image_stream.dart @@ -1047,6 +1047,10 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter { _frameDuration = _nextFrame!.duration; _nextFrame!.image.dispose(); _nextFrame = null; + if (_codec == null) { + // codec was disposed during _emitFrame + return; + } final int completedCycles = _framesEmitted ~/ _codec!.frameCount; if (_codec!.repetitionCount == -1 || completedCycles <= _codec!.repetitionCount) { _decodeNextFrameAndSchedule(); @@ -1108,7 +1112,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter { _nextFrame!.image.dispose(); _nextFrame = null; - _codec!.dispose(); + _codec?.dispose(); _codec = null; return; } diff --git a/packages/flutter/test/painting/image_stream_test.dart b/packages/flutter/test/painting/image_stream_test.dart index 58c2ad8c4a..c00a808f94 100644 --- a/packages/flutter/test/painting/image_stream_test.dart +++ b/packages/flutter/test/painting/image_stream_test.dart @@ -1076,4 +1076,37 @@ void main() { areCreateAndDispose, ); }); + + testWidgets('MultiFrameImageStreamCompleter image callback can remove listener', ( + WidgetTester tester, + ) async { + final Completer completer = Completer(); + 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); + + late ImageStreamListener streamListener; + + void listener(ImageInfo image, bool synchronousCall) { + addTearDown(image.dispose); + imageStream.removeListener(streamListener); + } + + 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); + await tester.idle(); + expect(mockCodec.disposed, true); + }); }