From 977dc71d952f405ceac26d1f00ff9e085bfd3234 Mon Sep 17 00:00:00 2001 From: Thomas Way Date: Fri, 20 Mar 2026 14:44:11 +0000 Subject: [PATCH] draft: simplify image cancelling --- .../animated_image_stream_completer.dart | 31 ++++++------------ ...ne_frame_multi_image_stream_completer.dart | 32 +++++++------------ mobile/pubspec.lock | 8 ++--- 3 files changed, 24 insertions(+), 47 deletions(-) diff --git a/mobile/lib/presentation/widgets/images/animated_image_stream_completer.dart b/mobile/lib/presentation/widgets/images/animated_image_stream_completer.dart index be4fbff8cf..b18aedb690 100644 --- a/mobile/lib/presentation/widgets/images/animated_image_stream_completer.dart +++ b/mobile/lib/presentation/widgets/images/animated_image_stream_completer.dart @@ -9,11 +9,7 @@ import 'package:flutter/painting.dart'; /// Codec is disposed through the MultiFrameImageStreamCompleter's internals onDispose method class AnimatedImageStreamCompleter extends MultiFrameImageStreamCompleter { void Function()? _onLastListenerRemoved; - int _listenerCount = 0; - // True once any image or the codec has been provided. - // Until then the image cache holds one listener, so "last real listener gone" - // is _listenerCount == 1, not 0. - bool didProvideImage = false; + ImageStreamListener? _cacheListener; AnimatedImageStreamCompleter._({ required super.codec, @@ -38,18 +34,15 @@ class AnimatedImageStreamCompleter extends MultiFrameImageStreamCompleter { ); if (initialImage != null) { - self.didProvideImage = true; self.setImage(initialImage); } stream.listen( (item) { if (item is ImageInfo) { - self.didProvideImage = true; self.setImage(item); } else if (item is ui.Codec) { if (!codecCompleter.isCompleted) { - self.didProvideImage = true; codecCompleter.complete(item); } } @@ -60,8 +53,6 @@ class AnimatedImageStreamCompleter extends MultiFrameImageStreamCompleter { } }, onDone: () { - // also complete if we are done but no error occurred, and we didn't call complete yet - // could happen on cancellation if (!codecCompleter.isCompleted) { codecCompleter.completeError(StateError('Stream closed without providing a codec')); } @@ -73,24 +64,20 @@ class AnimatedImageStreamCompleter extends MultiFrameImageStreamCompleter { @override void addListener(ImageStreamListener listener) { + _cacheListener ??= listener; super.addListener(listener); - _listenerCount++; } @override void removeListener(ImageStreamListener listener) { super.removeListener(listener); - _listenerCount--; - - final bool onlyCacheListenerLeft = _listenerCount == 1 && !didProvideImage; - final bool noListenersAfterCodec = _listenerCount == 0 && didProvideImage; - - if (onlyCacheListenerLeft || noListenersAfterCodec) { - final onLastListenerRemoved = _onLastListenerRemoved; - if (onLastListenerRemoved != null) { - _onLastListenerRemoved = null; - onLastListenerRemoved(); - } + if (listener != _cacheListener) { + _cancel(); } } + + void _cancel() { + _onLastListenerRemoved?.call(); + _onLastListenerRemoved = null; + } } diff --git a/mobile/lib/presentation/widgets/images/one_frame_multi_image_stream_completer.dart b/mobile/lib/presentation/widgets/images/one_frame_multi_image_stream_completer.dart index 302deca4a7..d94fae0655 100644 --- a/mobile/lib/presentation/widgets/images/one_frame_multi_image_stream_completer.dart +++ b/mobile/lib/presentation/widgets/images/one_frame_multi_image_stream_completer.dart @@ -10,9 +10,7 @@ import 'package:flutter/painting.dart'; /// An ImageStreamCompleter with support for loading multiple images. class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter { void Function()? _onLastListenerRemoved; - int _listenerCount = 0; - // True once setImage() has been called at least once. - bool didProvideImage = false; + ImageStreamListener? _cacheListener; /// The constructor to create an OneFramePlaceholderImageStreamCompleter. The [images] /// should be the primary images to display (typically asynchronously as they load). @@ -23,17 +21,12 @@ class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter { ImageInfo? initialImage, InformationCollector? informationCollector, void Function()? onLastListenerRemoved, - }) { + }) : _onLastListenerRemoved = onLastListenerRemoved { if (initialImage != null) { - didProvideImage = true; setImage(initialImage); } - _onLastListenerRemoved = onLastListenerRemoved; images.listen( - (image) { - didProvideImage = true; - setImage(image); - }, + (image) => setImage(image), onError: (Object error, StackTrace stack) { reportError( context: ErrorDescription('resolving a single-frame image stream'), @@ -48,23 +41,20 @@ class OneFramePlaceholderImageStreamCompleter extends ImageStreamCompleter { @override void addListener(ImageStreamListener listener) { + _cacheListener ??= listener; super.addListener(listener); - _listenerCount = _listenerCount + 1; } @override void removeListener(ImageStreamListener listener) { super.removeListener(listener); - _listenerCount = _listenerCount - 1; - - final bool onlyCacheListenerLeft = _listenerCount == 1 && !didProvideImage; - final bool noListenersAfterImage = _listenerCount == 0 && didProvideImage; - - final onLastListenerRemoved = _onLastListenerRemoved; - - if (onLastListenerRemoved != null && (noListenersAfterImage || onlyCacheListenerLeft)) { - _onLastListenerRemoved = null; - onLastListenerRemoved(); + if (listener != _cacheListener) { + _cancel(); } } + + void _cancel() { + _onLastListenerRemoved?.call(); + _onLastListenerRemoved = null; + } } diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 89a43f328b..9d5f431792 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1194,10 +1194,10 @@ packages: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.16.0" mime: dependency: transitive description: @@ -1897,10 +1897,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.6" thumbhash: dependency: "direct main" description: