Skip to content

Commit 038045e

Browse files
committed
fix(message_widget): guard MessageCard._updateWidthLimit against unlaid RenderBox
`_MessageCardState._updateWidthLimit` is scheduled via `WidgetsBinding.instance.addPostFrameCallback` from `didChangeDependencies`, and reads `renderBox.size.width` when it fires. If the attachments subtree has been detached from the tree between scheduling and firing (e.g. the parent removed the message because the list was rebuilt, the chat was disposed, or the attachment was deleted), `RenderBox.size` throws `StateError: RenderBox was not laid out` per the framework contract. The throw is caught by `FlutterError.onError`, so users see a dropped frame rather than a crash — but Sentry still records it as a fatal error. In one production Flutter app the two corresponding Sentry issues have generated 1500+ events across 1100+ unique users in a single 2-week release. The error appears under any view path that has chat messages with attachments (chat, groupPost, splash, root). Fix is a `hasSize` guard before reading `.size`, plus moving the existing `mounted` check to the top of the function for symmetry (it was previously only checked before `setState`, so we still touched the unmounted render object first). The render-box-was-not-laid-out error is the canonical signature of this mistake in Flutter; the `hasSize` check is exactly what the framework docs recommend for this case.
1 parent 7f0804d commit 038045e

2 files changed

Lines changed: 12 additions & 5 deletions

File tree

packages/stream_chat_flutter/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
until the server confirmed it.
77
- Fixed a `FlutterError` ("A RenderViewport exceeded its maximum number of layout cycles") that
88
could occur when fast-scrolling through the message list.
9+
- Fixed `RenderBox was not laid out` thrown by `MessageCard._updateWidthLimit` when the attachments
10+
subtree was detached between scheduling the post-frame callback and it firing (e.g. the list was
11+
rebuilt or the message removed). Added a `hasSize` guard before reading `RenderBox.size`.
912

1013
## 9.24.0
1114

packages/stream_chat_flutter/lib/src/message_widget/message_card.dart

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,19 @@ class _MessageCardState extends State<MessageCard> {
133133
}
134134

135135
void _updateWidthLimit() {
136+
if (!mounted) return;
137+
136138
final attachmentContext = attachmentsKey.currentContext;
137139
final renderBox = attachmentContext?.findRenderObject() as RenderBox?;
138-
final attachmentsWidth = renderBox?.size.width;
140+
// The attachments subtree may have been detached between scheduling this
141+
// post-frame callback and it firing. Reading [RenderBox.size] without
142+
// checking [RenderBox.hasSize] throws `RenderBox was not laid out`.
143+
if (renderBox == null || !renderBox.hasSize) return;
144+
final attachmentsWidth = renderBox.size.width;
139145

140-
if (attachmentsWidth == null || attachmentsWidth == 0) return;
146+
if (attachmentsWidth == 0) return;
141147

142-
if (mounted) {
143-
setState(() => widthLimit = attachmentsWidth);
144-
}
148+
setState(() => widthLimit = attachmentsWidth);
145149
}
146150

147151
@override

0 commit comments

Comments
 (0)