diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart index a18aa64865e..d742c34fabb 100644 --- a/packages/flutter/lib/src/widgets/image.dart +++ b/packages/flutter/lib/src/widgets/image.dart @@ -1079,8 +1079,8 @@ class _ImageState extends State with WidgetsBindingObserver { super.didUpdateWidget(oldWidget); if (_isListeningToStream && (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) { - _imageStream.removeListener(_getListener(oldWidget.loadingBuilder)); - _imageStream.addListener(_getListener()); + _imageStream.removeListener(_getListener()); + _imageStream.addListener(_getListener(recreateListener: true)); } if (widget.image != oldWidget.image) _resolveImage(); @@ -1119,22 +1119,25 @@ class _ImageState extends State with WidgetsBindingObserver { _updateSourceStream(newStream); } - ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) { - loadingBuilder ??= widget.loadingBuilder; - _lastException = null; - _lastStack = null; - return ImageStreamListener( - _handleImageFrame, - onChunk: loadingBuilder == null ? null : _handleImageChunk, - onError: widget.errorBuilder != null - ? (dynamic error, StackTrace stackTrace) { - setState(() { - _lastException = error; - _lastStack = stackTrace; - }); - } - : null, - ); + ImageStreamListener _imageStreamListener; + ImageStreamListener _getListener({bool recreateListener = false}) { + if(_imageStreamListener == null || recreateListener) { + _lastException = null; + _lastStack = null; + _imageStreamListener = ImageStreamListener( + _handleImageFrame, + onChunk: widget.loadingBuilder == null ? null : _handleImageChunk, + onError: widget.errorBuilder != null + ? (dynamic error, StackTrace stackTrace) { + setState(() { + _lastException = error; + _lastStack = stackTrace; + }); + } + : null, + ); + } + return _imageStreamListener; } void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) { diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart index bea9a12d7d5..919a10d11e6 100644 --- a/packages/flutter/test/widgets/image_test.dart +++ b/packages/flutter/test/widgets/image_test.dart @@ -1331,6 +1331,73 @@ void main() { expect(tester.binding.hasScheduledFrame, isFalse); }, skip: isBrowser); + testWidgets('Verify Image resets its ImageListeners', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + final TestImageStreamCompleter imageStreamCompleter = TestImageStreamCompleter(); + final TestImageProvider imageProvider1 = TestImageProvider(streamCompleter: imageStreamCompleter); + await tester.pumpWidget( + Container( + key: key, + child: Image( + image: imageProvider1, + ), + ), + ); + // listener from resolveStreamForKey is always added. + expect(imageStreamCompleter.listeners.length, 2); + + + final TestImageProvider imageProvider2 = TestImageProvider(); + await tester.pumpWidget( + Container( + key: key, + child: Image( + image: imageProvider2, + excludeFromSemantics: true, + ), + ), + null, + EnginePhase.layout, + ); + + // only listener from resolveStreamForKey is left. + expect(imageStreamCompleter.listeners.length, 1); + }); + + testWidgets('Verify Image resets its ErrorListeners', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + final TestImageStreamCompleter imageStreamCompleter = TestImageStreamCompleter(); + final TestImageProvider imageProvider1 = TestImageProvider(streamCompleter: imageStreamCompleter); + await tester.pumpWidget( + Container( + key: key, + child: Image( + image: imageProvider1, + errorBuilder: (_,__,___) => Container(), + ), + ), + ); + // listener from resolveStreamForKey is always added. + expect(imageStreamCompleter.listeners.length, 2); + + + final TestImageProvider imageProvider2 = TestImageProvider(); + await tester.pumpWidget( + Container( + key: key, + child: Image( + image: imageProvider2, + excludeFromSemantics: true, + ), + ), + null, + EnginePhase.layout, + ); + + // only listener from resolveStreamForKey is left. + expect(imageStreamCompleter.listeners.length, 1); + }); + testWidgets('Image defers loading while fast scrolling', (WidgetTester tester) async { const int gridCells = 1000; final List imageProviders = [];