mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
[H] Add ImageStreamCompleter.hasListeners (and cleanup) (#25865)
* Remove stray extra space * Add ImageStreamCompleter.hasListeners (and cleanup) This is mostly just some cleanup of stuff I ran into, but it makes `hasListeners` protected on `ImageStreamCompleter`, because otherwise there's no way to track if listeners are registered or not. * Address review comments
This commit is contained in:
parent
88fc38cf9b
commit
d5d47f6384
|
@ -59,6 +59,11 @@ class FlutterErrorDetails {
|
||||||
|
|
||||||
/// A human-readable description of where the error was caught (as opposed to
|
/// A human-readable description of where the error was caught (as opposed to
|
||||||
/// where it was thrown).
|
/// where it was thrown).
|
||||||
|
///
|
||||||
|
/// The string should be in a form that will make sense in English when
|
||||||
|
/// following the word "thrown", as in "thrown while obtaining the image from
|
||||||
|
/// the network" (for the context "while obtaining the image from the
|
||||||
|
/// network").
|
||||||
final String context;
|
final String context;
|
||||||
|
|
||||||
/// A callback which filters the [stack] trace. Receives an iterable of
|
/// A callback which filters the [stack] trace. Receives an iterable of
|
||||||
|
|
|
@ -2268,7 +2268,7 @@ class InputDecoration {
|
||||||
/// can be expanded beyond that. Anything larger than 24px will require
|
/// can be expanded beyond that. Anything larger than 24px will require
|
||||||
/// additional padding to ensure it matches the material spec of 12px padding
|
/// additional padding to ensure it matches the material spec of 12px padding
|
||||||
/// between the right edge of the input and trailing edge of the prefix icon.
|
/// between the right edge of the input and trailing edge of the prefix icon.
|
||||||
/// The following snipped shows how to pad the trailing edge of the suffix
|
/// The following snippet shows how to pad the trailing edge of the suffix
|
||||||
/// icon:
|
/// icon:
|
||||||
///
|
///
|
||||||
/// ```dart
|
/// ```dart
|
||||||
|
|
|
@ -61,7 +61,7 @@ class ImageInfo {
|
||||||
/// Used by [ImageStream].
|
/// Used by [ImageStream].
|
||||||
///
|
///
|
||||||
/// The `synchronousCall` argument is true if the listener is being invoked
|
/// The `synchronousCall` argument is true if the listener is being invoked
|
||||||
/// during the call to addListener. This can be useful if, for example,
|
/// during the call to `addListener`. This can be useful if, for example,
|
||||||
/// [ImageStream.addListener] is invoked during a frame, so that a new rendering
|
/// [ImageStream.addListener] is invoked during a frame, so that a new rendering
|
||||||
/// frame is requested if the call was asynchronous (after the current frame)
|
/// frame is requested if the call was asynchronous (after the current frame)
|
||||||
/// and no rendering frame is requested if the call was synchronous (within the
|
/// and no rendering frame is requested if the call was synchronous (within the
|
||||||
|
@ -148,9 +148,13 @@ class ImageStream extends Diagnosticable {
|
||||||
/// `listener`. If an error occurred, `onError` will be called instead of
|
/// `listener`. If an error occurred, `onError` will be called instead of
|
||||||
/// `listener`.
|
/// `listener`.
|
||||||
///
|
///
|
||||||
/// Many `listener`s can have the same `onError` and one `listener` can also
|
/// If a `listener` or `onError` handler is registered multiple times, then it
|
||||||
/// have multiple `onError` by invoking [addListener] multiple times with
|
/// will be called multiple times when the image stream completes (whether
|
||||||
/// a different `onError` each time.
|
/// because a new image is available or because an error occurs,
|
||||||
|
/// respectively). In general, registering a listener multiple times is
|
||||||
|
/// discouraged because [removeListener] will remove the first instance that
|
||||||
|
/// was added, even if it was added with a different `onError` than the
|
||||||
|
/// intended paired `addListener` call.
|
||||||
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
||||||
if (_completer != null)
|
if (_completer != null)
|
||||||
return _completer.addListener(listener, onError: onError);
|
return _completer.addListener(listener, onError: onError);
|
||||||
|
@ -160,11 +164,26 @@ class ImageStream extends Diagnosticable {
|
||||||
|
|
||||||
/// Stop listening for new concrete [ImageInfo] objects and errors from
|
/// Stop listening for new concrete [ImageInfo] objects and errors from
|
||||||
/// the `listener`'s associated [ImageErrorListener].
|
/// the `listener`'s associated [ImageErrorListener].
|
||||||
|
///
|
||||||
|
/// If `listener` has been added multiple times, this removes the first
|
||||||
|
/// instance of the listener, along with the `onError` listener that was
|
||||||
|
/// registered with that first instance. This might not be the instance that
|
||||||
|
/// the `addListener` corresponding to this `removeListener` had added.
|
||||||
|
///
|
||||||
|
/// For example, if one widget calls [addListener] with a global static
|
||||||
|
/// function and a private error handler, and another widget calls
|
||||||
|
/// [addListener] with the same global static function but a different private
|
||||||
|
/// error handler, then the second widget is disposed and removes the image
|
||||||
|
/// listener (the aforementioned global static function), it will remove the
|
||||||
|
/// error handler from the first widget, not the second. If an error later
|
||||||
|
/// occurs, the first widget, which is still supposedly listening, will not
|
||||||
|
/// receive any messages, while the second, which is supposedly disposed, will
|
||||||
|
/// have its callback invoked.
|
||||||
void removeListener(ImageListener listener) {
|
void removeListener(ImageListener listener) {
|
||||||
if (_completer != null)
|
if (_completer != null)
|
||||||
return _completer.removeListener(listener);
|
return _completer.removeListener(listener);
|
||||||
assert(_listeners != null);
|
assert(_listeners != null);
|
||||||
for (int i = 0; i < _listeners.length; ++i) {
|
for (int i = 0; i < _listeners.length; i += 1) {
|
||||||
if (_listeners[i].listener == listener) {
|
if (_listeners[i].listener == listener) {
|
||||||
_listeners.removeAt(i);
|
_listeners.removeAt(i);
|
||||||
break;
|
break;
|
||||||
|
@ -216,6 +235,24 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
ImageInfo _currentImage;
|
ImageInfo _currentImage;
|
||||||
FlutterErrorDetails _currentError;
|
FlutterErrorDetails _currentError;
|
||||||
|
|
||||||
|
/// Whether any listeners are currently registered.
|
||||||
|
///
|
||||||
|
/// Clients should not depend on this value for their behavior, because having
|
||||||
|
/// one listener's logic change when another listener happens to start or stop
|
||||||
|
/// listening will lead to extremely hard-to-track bugs. Subclasses might use
|
||||||
|
/// this information to determine whether to do any work when there are no
|
||||||
|
/// listeners, however; for example, [MultiFrameImageStreamCompleter] uses it
|
||||||
|
/// to determine when to iterate through frames of an animated image.
|
||||||
|
///
|
||||||
|
/// Typically this is used by overriding [addListener], checking if
|
||||||
|
/// [hasListeners] is false before calling `super.addListener()`, and if so,
|
||||||
|
/// starting whatever work is needed to determine when to call
|
||||||
|
/// [notifyListeners]; and similarly, by overriding [removeListener], checking
|
||||||
|
/// if [hasListeners] is false after calling `super.removeListener()`, and if
|
||||||
|
/// so, stopping that same work.
|
||||||
|
@protected
|
||||||
|
bool get hasListeners => _listeners.isNotEmpty;
|
||||||
|
|
||||||
/// Adds a listener callback that is called whenever a new concrete [ImageInfo]
|
/// Adds a listener callback that is called whenever a new concrete [ImageInfo]
|
||||||
/// object is available or an error is reported. If a concrete image is
|
/// object is available or an error is reported. If a concrete image is
|
||||||
/// already available, or if an error has been already reported, this object
|
/// already available, or if an error has been already reported, this object
|
||||||
|
@ -228,6 +265,18 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
/// occurred. If the listener is added within a render object paint function,
|
/// occurred. If the listener is added within a render object paint function,
|
||||||
/// then use this flag to avoid calling [RenderObject.markNeedsPaint] during
|
/// then use this flag to avoid calling [RenderObject.markNeedsPaint] during
|
||||||
/// a paint.
|
/// a paint.
|
||||||
|
///
|
||||||
|
/// An [ImageErrorListener] can also optionally be added along with the
|
||||||
|
/// `listener`. If an error occurred, `onError` will be called instead of
|
||||||
|
/// `listener`.
|
||||||
|
///
|
||||||
|
/// If a `listener` or `onError` handler is registered multiple times, then it
|
||||||
|
/// will be called multiple times when the image stream completes (whether
|
||||||
|
/// because a new image is available or because an error occurs,
|
||||||
|
/// respectively). In general, registering a listener multiple times is
|
||||||
|
/// discouraged because [removeListener] will remove the first instance that
|
||||||
|
/// was added, even if it was added with a different `onError` than the
|
||||||
|
/// intended paired `addListener` call.
|
||||||
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
||||||
_listeners.add(_ImageListenerPair(listener, onError));
|
_listeners.add(_ImageListenerPair(listener, onError));
|
||||||
if (_currentImage != null) {
|
if (_currentImage != null) {
|
||||||
|
@ -259,8 +308,13 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
|
|
||||||
/// Stop listening for new concrete [ImageInfo] objects and errors from
|
/// Stop listening for new concrete [ImageInfo] objects and errors from
|
||||||
/// its associated [ImageErrorListener].
|
/// its associated [ImageErrorListener].
|
||||||
|
///
|
||||||
|
/// If `listener` has been added multiple times, this removes the first
|
||||||
|
/// instance of the listener, along with the `onError` listener that was
|
||||||
|
/// registered with that first instance. This might not be the instance that
|
||||||
|
/// the `addListener` corresponding to this `removeListener` had added.
|
||||||
void removeListener(ImageListener listener) {
|
void removeListener(ImageListener listener) {
|
||||||
for (int i = 0; i < _listeners.length; ++i) {
|
for (int i = 0; i < _listeners.length; i += 1) {
|
||||||
if (_listeners[i].listener == listener) {
|
if (_listeners[i].listener == listener) {
|
||||||
_listeners.removeAt(i);
|
_listeners.removeAt(i);
|
||||||
break;
|
break;
|
||||||
|
@ -295,6 +349,29 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
///
|
///
|
||||||
/// If no error listeners are attached, a [FlutterError] will be reported
|
/// If no error listeners are attached, a [FlutterError] will be reported
|
||||||
/// instead.
|
/// instead.
|
||||||
|
///
|
||||||
|
/// The `context` should be a string describing where the error was caught, in
|
||||||
|
/// a form that will make sense in English when following the word "thrown",
|
||||||
|
/// as in "thrown while obtaining the image from the network" (for the context
|
||||||
|
/// "while obtaining the image from the network").
|
||||||
|
///
|
||||||
|
/// The `exception` is the error being reported; the `stack` is the
|
||||||
|
/// [StackTrace] associated with the exception.
|
||||||
|
///
|
||||||
|
/// The `informationCollector` is a callback (of type [InformationCollector])
|
||||||
|
/// that is called when the exception is used by [FlutterError.reportError].
|
||||||
|
/// It is used to obtain further details to include in the logs, which may be
|
||||||
|
/// expensive to collect, and thus should only be collected if the error is to
|
||||||
|
/// be logged in the first place.
|
||||||
|
///
|
||||||
|
/// The `silent` argument causes the exception to not be reported to the logs
|
||||||
|
/// in release builds, if passed to [FlutterError.reportError]. (It is still
|
||||||
|
/// sent to error handlers.) It should be set to true if the error is one that
|
||||||
|
/// is expected to be encountered in release builds, for example network
|
||||||
|
/// errors. That way, logs on end-user devices will not have spurious
|
||||||
|
/// messages, but errors during development will still be reported.
|
||||||
|
///
|
||||||
|
/// See [FlutterErrorDetails] for further details on these values.
|
||||||
@protected
|
@protected
|
||||||
void reportError({
|
void reportError({
|
||||||
String context,
|
String context,
|
||||||
|
@ -313,11 +390,11 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<ImageErrorListener> localErrorListeners =
|
final List<ImageErrorListener> localErrorListeners =
|
||||||
_listeners.map<ImageErrorListener>(
|
_listeners.map<ImageErrorListener>(
|
||||||
(_ImageListenerPair listenerPair) => listenerPair.errorListener
|
(_ImageListenerPair listenerPair) => listenerPair.errorListener
|
||||||
).where(
|
).where(
|
||||||
(ImageErrorListener errorListener) => errorListener != null
|
(ImageErrorListener errorListener) => errorListener != null
|
||||||
).toList();
|
).toList();
|
||||||
|
|
||||||
if (localErrorListeners.isEmpty) {
|
if (localErrorListeners.isEmpty) {
|
||||||
FlutterError.reportError(_currentError);
|
FlutterError.reportError(_currentError);
|
||||||
|
@ -328,7 +405,7 @@ abstract class ImageStreamCompleter extends Diagnosticable {
|
||||||
} catch (exception, stack) {
|
} catch (exception, stack) {
|
||||||
FlutterError.reportError(
|
FlutterError.reportError(
|
||||||
FlutterErrorDetails(
|
FlutterErrorDetails(
|
||||||
context: 'by an image error listener',
|
context: 'when reporting an error to an image listener',
|
||||||
library: 'image resource service',
|
library: 'image resource service',
|
||||||
exception: exception,
|
exception: exception,
|
||||||
stack: stack,
|
stack: stack,
|
||||||
|
@ -465,7 +542,7 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleAppFrame(Duration timestamp) {
|
void _handleAppFrame(Duration timestamp) {
|
||||||
if (!_hasActiveListeners)
|
if (!hasListeners)
|
||||||
return;
|
return;
|
||||||
if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
|
if (_isFirstFrame() || _hasFrameDurationPassed(timestamp)) {
|
||||||
_emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));
|
_emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));
|
||||||
|
@ -520,20 +597,17 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
|
||||||
_framesEmitted += 1;
|
_framesEmitted += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _hasActiveListeners => _listeners.isNotEmpty;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
void addListener(ImageListener listener, { ImageErrorListener onError }) {
|
||||||
if (!_hasActiveListeners && _codec != null) {
|
if (!hasListeners && _codec != null)
|
||||||
_decodeNextFrameAndSchedule();
|
_decodeNextFrameAndSchedule();
|
||||||
}
|
|
||||||
super.addListener(listener, onError: onError);
|
super.addListener(listener, onError: onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void removeListener(ImageListener listener) {
|
void removeListener(ImageListener listener) {
|
||||||
super.removeListener(listener);
|
super.removeListener(listener);
|
||||||
if (!_hasActiveListeners) {
|
if (!hasListeners) {
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timer = null;
|
_timer = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import '../flutter_test_alternative.dart';
|
import '../flutter_test_alternative.dart';
|
||||||
|
|
||||||
int yieldCount;
|
int yieldCount;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2016 The Chromium Authors. All rights reserved.
|
// Copyright 2016 The Chromium Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue