Add reportErrors to ImageStreamListener#180327
Conversation
56e68e5 to
65a91aa
Compare
65a91aa to
f3d3c60
Compare
| }); | ||
|
|
||
| testWidgets( | ||
| 'errorBuilder prevents FlutterError report only if errorBuilder is non-null when widget is disposed', |
There was a problem hiding this comment.
I've updated this test to assert that errors are silenced when the widget is disposed.
If the behavior asserted by the original test satisfies the intended design, then issue #81931 should be closed as "working as intended".
justinmc
left a comment
There was a problem hiding this comment.
Thanks for the clear description of the solution. I don't know the original intention of this code but I suspect your approach is right. I left a few minor comments.
There was a problem hiding this comment.
These docs are no longer strictly true, could you update them?
| final listener = ImageStreamListener( | ||
| (ImageInfo info, bool syncCall) {}, | ||
| onError: (Object exception, StackTrace? stackTrace) { | ||
| throw exception; | ||
| }, | ||
| ); | ||
| stream.addListener(listener); |
There was a problem hiding this comment.
I guess this turns the listener into an "active" listener?
|
|
||
| expect(imageCache.statusForKey(provider).untracked, true); | ||
| expect(imageCache.pendingImageCount, 0); | ||
| stream.removeListener(listener); |
There was a problem hiding this comment.
I think it would be better to do this in an addTeardown just after your call to addListener, to avoid leaks.
There was a problem hiding this comment.
Same for the other tests below.
There was a problem hiding this comment.
Thanks for the review. I fixed in 5fe7eeb.
justinmc
left a comment
There was a problem hiding this comment.
Sorry now I'm second guessing myself on this PR, because I worry about swallowing errors that someone might care about. Why is the current behavior of reporting the error with FlutterErrorDetails.silent still too much? I guess it's just showing up in the real world (though debug mode) too frequently during normal development? Would it help if we added to the error message to explain that the situation more?
|
Thanks for the feedback. I understand the concern about swallowing errors. The This follows naturally from the I'd like to propose a more conservative approach. Instead of my current implementation, track whether an
This limits the change to What do you think? I'll update the PR if this direction looks good. For context: in one of the projects I'm involved in, this code path is consistently the top error in Crashlytics dashboards despite being handled by |
|
I ran your example code and looked at the Image and ImageStreamListener code a bit more. I think that the current behavior makes sense in the context of ImageStreamListener alone. Your change (the original one currently in this branch) blends the concern of Image into ImageStreamListener. Where do you expect to implement your new idea with _hasHadActiveErrorListener, is that in Image or ImageStreamListener? What if we dispose the ImageStreamListener when Image is disposed and then ImageStreamListener throws away errors if they come back after it has been disposed? That makes sense to me both from the self-contained perspective of Image and ImageStreamListener. |
|
Thank you for the thoughtful feedback. I've reworked the approach based on your concerns. Regarding swallowing errors: The previous approach silenced all errors when no active listeners remained, which was too broad. The new approach adds an explicit Regarding the disposal approach: There is actually an existing mechanism that takes a similar approach: However, when the image URL changes (e.g. rapid user interaction),
ref: #167783 |
justinmc
left a comment
There was a problem hiding this comment.
LGTM with some minor comments. I think this is the best approach I can think of for this problem. Sorry for the delayed review here!
| /// If the image stream has no error listeners (listeners with an [onError] | ||
| /// callback), the error is silently ignored. This prevents unnecessary | ||
| /// error noise when errors occur after the widget has been disposed. |
There was a problem hiding this comment.
Is this no longer true and should reference preventErrorReporting?
There was a problem hiding this comment.
Updated the doc to reflect the current behavior and reference reportErrors.
| /// | ||
| /// Defaults to false. The [Image] widget sets this to true when an | ||
| /// [Image.errorBuilder] is provided. | ||
| final bool preventErrorReporting; |
There was a problem hiding this comment.
There was a problem hiding this comment.
Or is reportErrorsAfterRemoval more descriptive? Though it's longer. Up to you!
There was a problem hiding this comment.
I renamed it to reportErrors and updated the docs!
| // Track that an error-preventing listener was once registered. | ||
| if (listener.preventErrorReporting) { | ||
| _hadErrorListener = true; | ||
| } |
There was a problem hiding this comment.
I guess there's no valid use case for removing a preventErrorReporting listener and wanting errors to be reported? In the case of the Image widget it makes sense as-is. Not sure if anything else might use this in this way.
There was a problem hiding this comment.
I also couldn't think of a use case beyond Image. Since reportErrors: false is an opt-in flag, I don't see a scenario where the same completer would later need to opt back in.
|
auto label is removed for flutter/flutter/180327, Failed to enqueue flutter/flutter/180327 with HTTP 400: Pull request Required status check "Merge Queue Guard" is expected.. |
|
autosubmit label was removed for flutter/flutter/180327, because - The status or check suite Linux analyze has failed. Please fix the issues identified (or deflake) before re-applying this label. |
|
Sorry, I fixed the code format. |
justinmc
left a comment
There was a problem hiding this comment.
Re LGTM, will autosubmit.
Roll Flutter from 81bc3d69535f to 707dbc0420a3 (85 revisions) flutter/flutter@81bc3d6...707dbc0 2026-05-01 [email protected] Removing TODOs from the WebParagraph code and addressing technical debts. (flutter/flutter#185168) 2026-05-01 [email protected] Ensure that vulkan_interface.h gets included before vk_mem_alloc.h (flutter/flutter#185777) 2026-05-01 [email protected] [flutter_tools] Bump dwds dependency to v27.1.1 (flutter/flutter#185357) 2026-05-01 [email protected] Roll Dart SDK from 6d4a319cbdac to 9aa7097f2e3e (3 revisions) (flutter/flutter#185888) 2026-05-01 [email protected] Roll Skia from fa1dcb289709 to 7ac6d42f2fd0 (1 revision) (flutter/flutter#185887) 2026-05-01 [email protected] Roll Skia from 54cc00adde38 to fa1dcb289709 (3 revisions) (flutter/flutter#185880) 2026-05-01 [email protected] [iOS] Migrate to FlutterFMLTaskRunner(s) (flutter/flutter#185815) 2026-05-01 [email protected] Remove material imports from navigator_on_did_remove_page_test and scrollable_in_overlay_test (flutter/flutter#182546) 2026-05-01 [email protected] Roll Skia from 2e279266f06a to 54cc00adde38 (3 revisions) (flutter/flutter#185872) 2026-05-01 [email protected] dev: Remove unused parameters (flutter/flutter#185345) 2026-05-01 [email protected] Roll Fuchsia Linux SDK from HN5VYzftnf_B8T-n9... to VnzuUefDQR0UhQ1L1... (flutter/flutter#185870) 2026-05-01 [email protected] Use g_free when using glib memory allocation (flutter/flutter#185519) 2026-05-01 [email protected] Roll Dart SDK from d30df3428f2e to 6d4a319cbdac (2 revisions) (flutter/flutter#185862) 2026-05-01 [email protected] Remove trivial test utility cross-imports from material and cupertino… (flutter/flutter#184295) 2026-05-01 [email protected] Inline test callback painter in tab scaffold test (flutter/flutter#184851) 2026-05-01 [email protected] [a11y] Add support for high contrast themes in the a11y assessments app (flutter/flutter#185801) 2026-05-01 [email protected] [a11y assessment app] Use default color for banner (flutter/flutter#185804) 2026-04-30 [email protected] Added name to AUTHORS (flutter/flutter#185586) 2026-04-30 [email protected] Remove semantics_tester import from raw_material_button_test.dart (flutter/flutter#184806) 2026-04-30 [email protected] Remove semantics_tester import from user_accounts_drawer_header_test.dart (flutter/flutter#184809) 2026-04-30 [email protected] Roll Skia from 7b88c5c281e5 to 2e279266f06a (5 revisions) (flutter/flutter#185854) 2026-04-30 [email protected] Handle symmetric rectangular and elliptical round super ellipses in the uber SDF renderer (flutter/flutter#185695) 2026-04-30 [email protected] Match on process name before killing for SwiftPM (flutter/flutter#185774) 2026-04-30 [email protected] Sync CHANGELOG.md from stable (flutter/flutter#185838) 2026-04-30 [email protected] Roll Dart SDK from 25910e31a6d2 to d30df3428f2e (5 revisions) (flutter/flutter#185839) 2026-04-30 [email protected] Check cross imports test subfolders (flutter/flutter#185493) 2026-04-30 [email protected] test: inline TestCallbackPainter in cupertino/picker_test.dart (flutter/flutter#185398) 2026-04-30 [email protected] Update customer testing version (flutter/flutter#185830) 2026-04-30 [email protected] Adapt the Metal shader library output list for debug versus release mode (flutter/flutter#185798) 2026-04-30 [email protected] [Impeller] Port a recent Vulkan swapchain fence waiting fix to the AHB version of the swapchain (flutter/flutter#185763) 2026-04-30 [email protected] Roll Skia from 26a59aa71eff to 7b88c5c281e5 (1 revision) (flutter/flutter#185821) 2026-04-30 [email protected] Roll Skia from 6b4167b4e204 to 26a59aa71eff (4 revisions) (flutter/flutter#185808) 2026-04-30 [email protected] [a11y] Mark SemanticsNode dirty when customSemanticsActions change (flutter/flutter#185707) 2026-04-30 [email protected] Roll Skia from 1bd2f1cc2746 to 6b4167b4e204 (8 revisions) (flutter/flutter#185799) 2026-04-30 [email protected] [iOS] Extract FlutterVSyncClient from vsync_waiter_ios (flutter/flutter#185737) 2026-04-30 [email protected] Roll Fuchsia Linux SDK from nnv8-SSam6yE8dw4z... to HN5VYzftnf_B8T-n9... (flutter/flutter#185782) 2026-04-29 [email protected] [iOS] Soften TaskRunner.postTask(delay:task:) delay check (flutter/flutter#185729) 2026-04-29 [email protected] Add reportErrors to ImageStreamListener (flutter/flutter#180327) 2026-04-29 [email protected] Roll Skia from f5c781c083c7 to 1bd2f1cc2746 (5 revisions) (flutter/flutter#185761) 2026-04-29 [email protected] Update merge semantics logic to merge sibling nodes (flutter/flutter#183745) 2026-04-29 [email protected] Roll Packages from ba80f8f to cde5b36 (12 revisions) (flutter/flutter#185748) 2026-04-29 [email protected] examples: Remove unused parameters (flutter/flutter#185346) 2026-04-29 [email protected] Update TickerMode.getValuesNotifier to handle initialization during State.dispose (flutter/flutter#185248) 2026-04-29 [email protected] Update triage links (flutter/flutter#185714) 2026-04-29 [email protected] Add support for high contrast and color inversion on Android (flutter/flutter#182263) 2026-04-29 [email protected] Roll Skia from 05251260fda6 to f5c781c083c7 (2 revisions) (flutter/flutter#185743) ...
fix #81931
Run the following app and quickly tap the FAB multiple times to output an exception to the console. However,
N/Acontinues to be displayed on the UI, anderrorBuilderis functioning correctly.Root Cause
ImageStreamCompleteroutlives theImagewidget becauseImageCacheholds keepAlive handles that prevent disposal even after the widget is gone. When the widget is disposed or rebuilt with a different URL, its listener is removed, but the completer continues to live. Errors arriving after that point have noonErrorhandler and fall through toFlutterError.reportError.The completer has no memory of whether an error handler was ever present, so it cannot distinguish between "no one ever handled errors" and "someone handled errors but is now gone." This is the root cause.
A previous fix (
addEphemeralErrorListenerin_stopListeningToStream) covers the dispose path, but not the rebuild path where_updateSourceStreamremoves the listener directly. It also has a limitation when a cached image is delivered first (_currentImage != null), causingaddEphemeralErrorListenerto skip registration and leaving subsequent network errors unhandled. Another fix inferred error-handling intent fromonError != null, but in debug modeonErroris always set for_debugBuildErrorWidget, making it impossible to distinguish real error handlers from debug-only re-throwers.Fix
Added
reportErrorsflag toImageStreamListener. TheImagewidget sets this tofalseonly whenerrorBuilderis provided.ImageStreamCompleterremembers that such a listener was once registered, and skips reporting errors toFlutterError.onErrorafter all listeners are removed.This separates "error handling intent" (
reportErrors) from "error callback presence" (onError != null), sokDebugModebehavior no longer interferes with error reporting logic.Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.