-
Notifications
You must be signed in to change notification settings - Fork 28.5k
Fix: Ensure Image.errorBuilder reliably prevents error reporting (with addEphemeralErrorListener
)
#167783
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Ensure Image.errorBuilder reliably prevents error reporting (with addEphemeralErrorListener
)
#167783
Conversation
3710426
to
5bc81d1
Compare
@@ -1180,6 +1180,21 @@ class _ImageState extends State<Image> with WidgetsBindingObserver { | |||
: null, | |||
), | |||
); | |||
if (widget.errorBuilder != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this support the following sequence?
- widget build with
widget.errorBuilder != null
- widget rebuild with
widget.errorBuilder == null
- widget dispose
- image failed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a great point! I've added a test for it and adjusted code to satisfy this case.
// allowing the disposal. For more details, see | ||
// https://github.com/flutter/flutter/issues/97077 . | ||
if (newStream.completer == null) { | ||
newStream.setCompleter(_EmptyImageCompleter()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you explain a bit why this is needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ephemeralListener is only applicable to image completers. However, a stream might not be resolved with a non-null completer (for various reasons; it's nullable anyway). This tricks is also used somewhere else, such as
stream.setCompleter(_ErrorImageCompleter()); |
// https://github.com/flutter/flutter/issues/97077 . | ||
if (widget.errorBuilder != null) { | ||
if (_imageStream!.completer == null) { | ||
_imageStream!.setCompleter(_EmptyImageCompleter()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is no completer, adding this won't do anything right? since it will never complete, should we just return in this case?
I looked at the code, and it looked like that even we set this completer and addEphemeralErrorListener, the EphemeralErrorListener will not carry over if the completer change later, so it won't be able to mute the error after that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that a stream without a completer will not cause any error, and it isn't useful to add ephemeral listeners any more?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean that a stream without a completer will not cause any error, and it isn't useful to add ephemeral listeners any more?
technically yes.
but i am more focusing on that adding an empty completer to add a ephemeral listener seems to be the same as not adding an ephemeral listener at all. The empty completer is never going to throw, and changing the completer won't carry the ephemeral listener over.
As for whether it should mute the error throw after changing the completer if widget.errorbuilder != null, I am not sure what the correct behavior should be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but i am more focusing on that adding an empty completer to add a ephemeral listener seems to be the same as not adding an ephemeral listener at all. The empty completer is never going to throw, and changing the completer won't carry the ephemeral listener over.
That makes sense. I'll look more into it and remove the empty listener if that's the case.
As for whether it should mute the error throw after changing the completer if widget.errorbuilder != null, I am not sure what the correct behavior should be.
For now it will mute the error again. It simply takes whatever case at the moment of widget disposal. I think this is an intuitive behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code seems to show that errors are only triggered from within the completer. Therefore when there's no completers there is no need to create one. I've removed the creation.
@chunhtai Can you take another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…rting (with `addEphemeralErrorListener`) (flutter/flutter#167783)
An alternative to #166130 using
addEphemeralErrorListener
.The following text is adapted from #166130.
Problem:
Currently, when using an
Image
widget with anerrorBuilder
, if the widget is removed from the widget tree (e.g., due to navigation orsetState
) after the image loading process has started but before an asynchronous loading error is reported back, the error can still be reported viaFlutterError.reportError
. This occurs because the_ImageState
listener is removed upon disposal, and theImageStreamCompleter
subsequently treats the error as unhandled, logging it despite the developer's intent to handle it via theerrorBuilder
. This leads to unexpected noise in logs and crash reporting systems.Solution:
This PR utilizes
addEphemeralErrorListener
, which allows the image stream to be disposed while having an error reporter. The error will not be reported as long as there is an error reporter. TheImage
widget adds an empty error reporter iferrorBuilder
is not null.Related Issues:
Tests:
errorBuilder prevents FlutterError report even if widget is disposed
totest/widgets/image_test.dart
to specifically verify the fix for the disposal race condition.test/widgets/image_test.dart
(including golden tests like 'Failed image loads in debug mode') pass with these changes without requiring updates.Breaking Changes:
errorBuilder
is present has shifted, but the user-facing outcome is consistent.Pre-launch Checklist
///
).If you need help, consider asking for advice on the #hackers-new channel on Discord.