draft: simplify image cancelling

This commit is contained in:
Thomas Way
2026-03-20 14:44:11 +00:00
parent e939fde6f1
commit 977dc71d95
3 changed files with 24 additions and 47 deletions
@@ -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;
}
}
@@ -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;
}
}