Skip to content

Conversation

@koji-1009
Copy link
Contributor

@koji-1009 koji-1009 commented Dec 27, 2025

fix #81931

Run the following app and quickly tap the FAB multiple times to output an exception to the console. However, N/A continues to be displayed on the UI, and errorBuilder is functioning correctly.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MyHomePage());
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  var counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Demo Home Page')),
      body: Center(
        child: Column(
          spacing: 16,
          mainAxisSize: MainAxisSize.min,
          children: [
            MyImage(
              url: switch (counter % 3) {
                0 => 'AAABBBCCC',
                1 => 'https://example.com/nothing',
                2 => 'https://google.com',
                _ => 'AAABBBCCC',
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            counter++;
          });
        },
        child: Text(counter.toString()),
      ),
    );
  }
}

class MyImage extends StatelessWidget {
  const MyImage({super.key, required this.url});

  final String url;

  @override
  Widget build(BuildContext context) {
    return Image.network(
      url,
      errorBuilder: (context, exception, stackTrace) {
        return Text('N/A');
      },
    );
  }
}

Root Cause Analysis

I investigated why the error is reported even after the widget is disposed. The main reason is that ImageCache keeps listening to the stream even after the Image widget is disposed.

Although the ImageCache listener is "passive" (it has no onError callback), the current implementation mistakenly treats its presence as "someone is listening." Since no one actually handles the error, it reports the error globally to FlutterError.onError. In essence, the system mistakes the cache listener for an active error handler.

The Fix

I added a check to see if there are any active listeners (listeners with an onError callback). If only passive listeners remain (like ImageCache), the error will now be silenced instead of reported globally. This effectively solves the issue of "abandoned" streams reporting errors after disposal.

Discussion

This change slightly updates the behavior in Release Mode when no errorBuilder is used. Previously, errors were reported to FlutterError.onError, but now they will be silent.

I believe this change is correct and necessary for the following reasons:

  1. If a developer does not set an errorBuilder, it implies they do not explicitly support or handle error cases for that widget.
  2. If a developer explicitly sets an errorBuilder, reporting these exceptions to FlutterError.onError is logically incorrect. FlutterError.onError is intended for unhandled exceptions, whereas these failures represent handled UI states, not application crashes.

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-assist bot 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.

@github-actions github-actions bot added the framework flutter/packages/flutter repository. See also f: labels. label Dec 27, 2025
@koji-1009 koji-1009 force-pushed the fix/image_exception branch 2 times, most recently from 56e68e5 to 65a91aa Compare December 27, 2025 05:44
});

testWidgets(
'errorBuilder prevents FlutterError report only if errorBuilder is non-null when widget is disposed',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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".

@koji-1009 koji-1009 marked this pull request as ready for review December 27, 2025 07:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Image.network throws uncaught exception when providing valid url without an image

1 participant