Fix: Ensure Image.errorBuilder reliably prevents error reporting (with addEphemeralErrorListener)#167783
Conversation
3710426 to
5bc81d1
Compare
| : null, | ||
| ), | ||
| ); | ||
| if (widget.errorBuilder != null) { |
There was a problem hiding this comment.
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.
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.
Can you explain a bit why this is needed?
There was a problem hiding this comment.
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
.| // 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.
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.
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.
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.
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.
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? |
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
…ing (with `addEphemeralErrorListener`) (flutter/flutter#167783)
An alternative to #166130 using
addEphemeralErrorListener.The following text is adapted from #166130.
Problem:
Currently, when using an
Imagewidget 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_ImageStatelistener is removed upon disposal, and theImageStreamCompletersubsequently 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. TheImagewidget adds an empty error reporter iferrorBuilderis not null.Related Issues:
Tests:
errorBuilder prevents FlutterError report even if widget is disposedtotest/widgets/image_test.dartto 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:
errorBuilderis 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.