From a55b6d426d5e865943d7417f9bce7d304f145476 Mon Sep 17 00:00:00 2001 From: Thomas SharedInbox Date: Thu, 14 May 2026 08:22:10 +0200 Subject: [PATCH] fix(R3): wrap flutter_html in error boundary to prevent screen crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds _SafeHtml — a StatefulWidget that intercepts build-phase exceptions from flutter_html via ErrorWidget.builder and falls back to an error message, so a malformed body cannot crash the entire EmailDetailScreen. Co-Authored-By: Claude Sonnet 4.6 --- lib/ui/screens/email_detail_screen.dart | 53 ++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/lib/ui/screens/email_detail_screen.dart b/lib/ui/screens/email_detail_screen.dart index 68c59b3..b31a52a 100644 --- a/lib/ui/screens/email_detail_screen.dart +++ b/lib/ui/screens/email_detail_screen.dart @@ -186,7 +186,7 @@ class _EmailDetailScreenState extends ConsumerState { ), ), ), - Html( + _SafeHtml( data: body.htmlBody!, extensions: [if (!_loadRemoteImages) _BlockRemoteImagesExtension()], ), @@ -501,6 +501,57 @@ class _UnsubscribeChip extends StatelessWidget { } } +/// Renders [Html] and falls back to an error message if the widget throws +/// during build, preventing a malformed body from crashing the whole screen. +class _SafeHtml extends StatefulWidget { + const _SafeHtml({required this.data, required this.extensions}); + final String data; + final List extensions; + + @override + State<_SafeHtml> createState() => _SafeHtmlState(); +} + +class _SafeHtmlState extends State<_SafeHtml> { + bool _failed = false; + + @override + Widget build(BuildContext context) { + if (_failed) { + return Padding( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + Icon( + Icons.warning_amber_outlined, + color: Theme.of(context).colorScheme.error, + size: 16, + ), + const SizedBox(width: 8), + const Expanded(child: Text('Message body could not be rendered.')), + ], + ), + ); + } + + // Intercept any build-phase throw from flutter_html for this subtree. + // We save/restore via postFrameCallback so other widgets are unaffected. + final prev = ErrorWidget.builder; + ErrorWidget.builder = (FlutterErrorDetails details) { + ErrorWidget.builder = prev; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() => _failed = true); + }); + return const SizedBox.shrink(); + }; + WidgetsBinding.instance.addPostFrameCallback( + (_) => ErrorWidget.builder = prev, + ); + + return Html(data: widget.data, extensions: widget.extensions); + } +} + class _BlockRemoteImagesExtension extends HtmlExtension { @override Set get supportedTags => {'img'};