You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[$175] [Sentry: APP-MHA] HybridApp iOS: FlashList onScroll throws "LayoutManager is not initialized" #104502
A native scroll event reaches a FlashList before its layout manager exists, or after it is torn down. FlashList's own onScroll calls recordInteraction(), which reads the window size and throws. The App already catches this error for the synthetic scroll path in src/components/MoneyRequestReportView/ExternalScrollFlashListTable.tsx (around line 114), but native scroll events go through FlashList's onScroll with no guard.
Reproduction
Unknown. See the Sentry events linked from the Sentry issue.
changed the title [-][Sentry: APP-MHA] HybridApp iOS: FlashList onScroll throws "LayoutManager is not initialized"[/-][+][$175] [Sentry: APP-MHA] HybridApp iOS: FlashList onScroll throws "LayoutManager is not initialized"[/+]on Oct 20, 2026
The throw is inside FlashList 2.3.2. Its onScroll calls recyclerViewManager.recordInteraction() once isInitialScrollComplete is true, and that calls ViewabilityManager.updateViewableItems(). updateViewableItems() calls getWindowSize(), which throws when layoutManager is undefined. The layoutManager is undefined before the first measure and is reset to undefined when it is marked dirty (RecyclerViewManager.js ~L157). FlashList's own computeItemViewability() sits behind shouldListenToVisibleIndices, but recordInteraction() has no hasLayout() check. A native scroll event that lands in that window therefore throws from the React event handler, and onerror reports it as fatal.
In our code src/components/Table/TableBody.tsx (the WorkspaceListTable list) and src/components/FlashList/index.tsx pass the FlashList straight through. Only the synthetic-scroll path in ExternalScrollFlashListTable.tsx has a try/catch. I could not reproduce this on a device. I confirmed the FlashList code path by reading node_modules.
What changes do you think we should make in order to solve the problem?
The cleanest fix is to guard recordInteraction in the library. Patch @shopify/flash-list with patch-package, which the repo already uses, so recordInteraction() or updateViewableItems() returns early when !hasLayout(). computeItemViewability already behaves this way.
This fixes the write site, so every FlashList in the app is covered, including the ones that don't wrap src/components/FlashList. I'd also send the same guard upstream to Shopify.
If a patch isn't wanted, the fallback is to stop un-guarded scroll events from reaching the list. This does not stop the throw from RecyclerView.onScroll itself, which is why I prefer the patch.
Add a patch note in patches/@shopify/flash-list/details.md, as the repo requires for patches.
Add a unit test next to the existing FlashList tests. Given a RecyclerViewManager with no layout, When recordInteraction() is called, Then it does not throw. It should fail on the unpatched code.
What alternative solutions did you explore? (Optional)
Wrap onScroll in try/catch in TableBody.tsx. This doesn't work, because FlashList throws before it calls the user's onScroll, and it would only cover one list.
Please re-state the problem that we are trying to solve in this issue.
On iOS HybridApp (Hermes / Fabric), scrolling a FlashList (e.g. on native screen Workspaces_List or during screen transitions) crashes the application with a fatal error: Error: LayoutManager is not initialized, window size is unavailable
thrown from @shopify/flash-list/dist/recyclerview/RecyclerViewManager.js -> getWindowSize(), called via recyclerViewManager.recordInteraction() from RecyclerView.js:269's onScroll handler.
What is the root cause of that problem?
On iOS Fabric, native scroll momentum events (onScroll) can be dispatched asynchronously from the native UI thread while React Native's layout pass has not yet completed (e.g., during navigation push/pop, or when the screen is hidden via display: none where our patch @shopify+flash-list+2.3.2+002+skip-layout-when-hidden.patch intentionally skips layout measurement).
When the native scroll event fires, RecyclerView.js's onScroll handler invokes:
recyclerViewManager.recordInteraction();
Inside RecyclerViewManager.js, recordInteraction() directly delegates to:
this.itemViewabilityManager.recordInteraction();
which triggers updateViewableItems() and calls this.recyclerViewManager.getWindowSize().
getWindowSize() requires an active layoutManager:
getWindowSize(){if(!this.layoutManager){thrownewError("LayoutManager is not initialized, window size is unavailable");}returnthis.layoutManager.getWindowSize();}
Because layoutManager is undefined before the first measurement or after the list is hidden/dirtied, calling recordInteraction() causes an unhandled exception that crashes the hybrid app.
What alternative solutions did you explore? (Optional)
Catching the error with try / catch inside ExternalScrollFlashListTable.tsx only protects the synthetic scroll path of MoneyRequestReportView, but does not protect native FlashLists throughout the app (such as Workspaces_List or SearchRouter). Fixing this at the FlashList patch level protects all lists globally.
What changes do you think we should make in order to solve the problem?
Add a patch in patches/@shopify/flash-list/ (or update patch 002 / create patch 017+fix-record-interaction-without-layout.patch):
In node_modules/@shopify/flash-list/dist/recyclerview/RecyclerViewManager.js (and src counterpart):
Guard recordInteraction() using hasLayout():
Update patches/@shopify/flash-list/details.md documenting this safeguard alongside the existing scrollToIndex guard in patch 002.
What automated tests can you add to prevent this from re-occurring?
Add a test in tests/unit/FlashListTest.tsx (or a unit test verifying RecyclerViewManager) that triggers recordInteraction() on a newly instantiated RecyclerViewManager whose layoutManager is undefined, verifying that it safely early-returns without throwing "LayoutManager is not initialized, window size is unavailable".
Contributor details
Your Expensify account email: [email protected]
Upwork Profile Link: N/A (Expensify NewDot Direct Settlement)
Proposal
Please re-state the problem that we are trying to solve in this issue.
On iOS HybridApp (Hermes / Fabric), during screen transitions or before/after layout computation (such as on Workspaces_List or back-navigation teardown), native scroll events dispatch to FlashList and crash the app with a fatal error: Error: LayoutManager is not initialized, window size is unavailable
thrown from @shopify/flash-list/dist/recyclerview/RecyclerViewManager.js -> getWindowSize(), called via itemViewabilityManager.recordInteraction().
What is the root cause of that problem?
When a native scroll event arrives from iOS Fabric, RecyclerView.js executes its onScroll handler:
The Broken API Contract Assumption: ViewabilityManager.js line 29 explicitly assumes that this.rvManager.getWindowSize() can return undefined when the window size / layout is not yet ready (if (listSize === undefined)).
However, RecyclerViewManager.js:118-123 does not return undefined; it throws unconditionally:
Because native momentum scroll events can fire asynchronously on iOS Fabric before layout measurement completes or after a screen starts tearing down (also noted in ExternalScrollFlashListTable.tsx:113-115), layoutManager is undefined, causing getWindowSize() to throw an unhandled fatal exception.
In addition, mutating this.hasInteracted = true before verifying layout means that if an interaction occurs prior to layout initialization, the list latches its interaction state prematurely before viewable items can actually be evaluated.
What changes do you think we should make in order to solve the problem?
We should patch @shopify/flash-list (following the exact pattern used in patches 002, 009, and 016):
Guard recordInteraction in node_modules/@shopify/flash-list/src/recyclerview/RecyclerViewManager.ts & dist/recyclerview/RecyclerViewManager.js:
Ensure recordInteraction() early-returns when hasLayout() is false, exactly like computeItemViewability():
This prevents premature latching of hasInteracted = true and preserves interaction tracking for the subsequent scroll once layout completes.
Guard updateViewableItems in node_modules/@shopify/flash-list/src/recyclerview/viewability/ViewabilityManager.ts & dist/recyclerview/viewability/ViewabilityManager.js:
Align the implementation with its intended design (handling uninitialized layout without throwing):
--- a/node_modules/@shopify/flash-list/dist/recyclerview/viewability/ViewabilityManager.js+++ b/node_modules/@shopify/flash-list/dist/recyclerview/viewability/ViewabilityManager.js@@ -26,6 +26,9 @@ export class ViewabilityManager {
this.updateViewableItems = (newViewableIndices) => {
var _a;
+ if (!this.rvManager.hasLayout()) {+ return;+ }
const listSize = this.rvManager.getWindowSize();
if (listSize === undefined || !this.shouldListenToVisibleIndices) {
return;
Register patch in patches/@shopify/flash-list/ & update details.md:
Add @shopify+flash-list+2.3.2+017+guard-viewability-record-interaction-layout.patch (or fold into patches/@shopify/flash-list/), with comprehensive documentation in patches/@shopify/flash-list/details.md following the repo's mandatory patch protocol.
What alternative solutions did you explore? (Optional)
Catching inside ExternalScrollFlashListTable.tsx / TableBody.tsx: ExternalScrollFlashListTable.tsx:112 already has a local try/catch for synthetic scroll events, but native screens (e.g. Workspaces_List, SearchRouter) use native FlashList scrolling where React event dispatches bypass local wrappers. A library-level patch is required to protect all FlashLists across all platforms.
Changing getWindowSize() to return undefined:
While ViewabilityManager.js line 29 expects undefined, changing getWindowSize() return signature affects TypeScript declarations and external consumers of RecyclerViewManager. Adding if (!this.hasLayout()) return; to recordInteraction() and updateViewableItems() cleanly stops the error at the source while maintaining strict API type contracts.
What specific scenarios should we cover in automated tests?
Add a unit test in tests/unit/FlashListTest.tsx (or RecyclerViewManagerTest.tsx):
Instantiate a RecyclerViewManager with mock props and layoutManager = undefined.
Call recordInteraction().
Assert that it does not throw Error: LayoutManager is not initialized, window size is unavailable.
Verify that once layoutManager is provided and initialized, calling recordInteraction() successfully updates viewability state.
Our own FlashList patch 002 (skip layout when hidden) causes this. When a list first mounts at 0×0 (a screen that is hidden or not sized yet), the patch skips the first layout. So FlashList never creates its layoutManager.
Upstream FlashList always creates it on the first layout, so upstream never hits this.
Until a real onLayout arrives, any native scroll event crashes the list. On iOS, UIKit can send a scroll event with no user touch, for example when it adjusts the content inset under the tab bar. FlashList's onScroll then calls recordInteraction() → updateViewableItems(). That reads getWindowSize() before it checks whether the list has any viewability listeners, and getWindowSize() throws.
Workspaces_List has no viewability callbacks, yet it still crashes because of that check order.
computeItemViewability() → computeVisibleIndices() throws the same way for lists that do use viewability. It has a shouldListenToVisibleIndices check but no hasLayout() check: RecyclerViewManager.ts:248
Once set, layoutManager is never cleared, so only a list that has never had a non-zero layout can crash. recordInteraction also exits early after its first call, so this can only happen on the first scroll event.
Upstream: v2.3.3 (latest) and main still have the same unguarded code. Shopify/flash-list has no issue or PR for this message.
What changes do you think we should make in order to solve the problem?
Extend patch 002 so the scroll path handles the "no layout yet" state it creates:
In ViewabilityManager.updateViewableItems, return early when there are no viewability listeners or rvManager.hasLayout() is false. Do both checks before calling getWindowSize(). This fixes every list without viewability callbacks, including Workspaces_List.
In RecyclerViewManager.recordInteraction, return early when hasLayout() is false. Then hasInteracted is not set before the list can measure anything, so waitForInteraction still works on the next scroll.
In RecyclerViewManager.computeItemViewability, add a hasLayout() check. This covers lists that use onViewableItemsChanged.
Update the patch 002 entry in patches/@shopify/flash-list/details.md.
Add a unit test: a RecyclerViewManager with no layout does not throw from recordInteraction() or computeItemViewability(), and viewability still fires after the first layout.
What alternative solutions did you explore? (Optional)
Remove patch 002. This fixes the crash but brings back the hidden-screen layout bugs the patch was added for.
Guard only recordInteraction, as the other proposals suggest. Lists with viewability callbacks would still crash through computeItemViewability.
try/catch in App components. FlashList throws before it calls our onScroll, so a try/catch in our code cannot catch it.
Confidence and what's unverified
The code path is confirmed. The exact iOS trigger is not verified on a device. It is either a UIKit inset adjustment or a tab-mount race on a list first measured at 0×0. Sentry breadcrumbs could confirm it: look for a tab switch or navigation just before the crash, with no onScrollBeginDrag. The fix works whichever trigger it is.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/104502) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
Hi @linhvovan29546 — a quick note for the review: as detailed in our proposal above, we specifically identified both RecyclerViewManager.recordInteraction() and ViewabilityManager.updateViewableItems() requiring the hasLayout() guards (preventing premature latching of hasInteracted = true and guarding before getWindowSize()). We can also include the defensive computeItemViewability() guard and the unit test in the patch/PR when assigned. Ready to submit the PR immediately upon approval. Thanks!
Activity
melvin-bot commentedon Oct 20, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @linhvovan29546 (
External)[-][Sentry: APP-MHA] HybridApp iOS: FlashList onScroll throws "LayoutManager is not initialized"[/-][+][$175] [Sentry: APP-MHA] HybridApp iOS: FlashList onScroll throws "LayoutManager is not initialized"[/+]melvin-bot commentedon Oct 20, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022112637743125227618
haiquywork commentedon Oct 21, 2026
Proposal
What is the root cause of that problem?
The throw is inside FlashList 2.3.2. Its
onScrollcallsrecyclerViewManager.recordInteraction()onceisInitialScrollCompleteis true, and that callsViewabilityManager.updateViewableItems().updateViewableItems()callsgetWindowSize(), which throws whenlayoutManageris undefined. ThelayoutManageris undefined before the first measure and is reset to undefined when it is marked dirty (RecyclerViewManager.js~L157). FlashList's owncomputeItemViewability()sits behindshouldListenToVisibleIndices, butrecordInteraction()has nohasLayout()check. A native scroll event that lands in that window therefore throws from the React event handler, andonerrorreports it as fatal.In our code
src/components/Table/TableBody.tsx(theWorkspaceListTablelist) andsrc/components/FlashList/index.tsxpass the FlashList straight through. Only the synthetic-scroll path inExternalScrollFlashListTable.tsxhas a try/catch. I could not reproduce this on a device. I confirmed the FlashList code path by readingnode_modules.App/src/components/MoneyRequestReportView/ExternalScrollFlashListTable.tsx
Lines 96 to 120 in 16d77a9
App/src/components/Table/TableBody.tsx
Lines 470 to 520 in 16d77a9
What changes do you think we should make in order to solve the problem?
recordInteractionin the library. Patch@shopify/flash-listwith patch-package, which the repo already uses, sorecordInteraction()orupdateViewableItems()returns early when!hasLayout().computeItemViewabilityalready behaves this way.src/components/FlashList. I'd also send the same guard upstream to Shopify.RecyclerView.onScrollitself, which is why I prefer the patch.patches/@shopify/flash-list/details.md, as the repo requires for patches.RecyclerViewManagerwith no layout, WhenrecordInteraction()is called, Then it does not throw. It should fail on the unpatched code.What alternative solutions did you explore? (Optional)
onScrollin try/catch inTableBody.tsx. This doesn't work, because FlashList throws before it calls the user'sonScroll, and it would only cover one list.jloa-dev commentedon Oct 21, 2026
Contributor details
Your Expensify account email: [email protected]
Upwork Profile Link: https://www.upwork.com/freelancers/~0163955a2ba221e513
Proposal
Please re-state the problem that we are trying to solve in this issue.
On iOS HybridApp (Hermes / Fabric), scrolling a FlashList (e.g. on native screen
Workspaces_Listor during screen transitions) crashes the application with a fatal error:Error: LayoutManager is not initialized, window size is unavailablethrown from
@shopify/flash-list/dist/recyclerview/RecyclerViewManager.js->getWindowSize(), called viarecyclerViewManager.recordInteraction()fromRecyclerView.js:269'sonScrollhandler.What is the root cause of that problem?
onScroll) can be dispatched asynchronously from the native UI thread while React Native's layout pass has not yet completed (e.g., during navigation push/pop, or when the screen is hidden viadisplay: nonewhere our patch@shopify+flash-list+2.3.2+002+skip-layout-when-hidden.patchintentionally skips layout measurement).RecyclerView.js'sonScrollhandler invokes:RecyclerViewManager.js,recordInteraction()directly delegates to:updateViewableItems()and callsthis.recyclerViewManager.getWindowSize().getWindowSize()requires an activelayoutManager:layoutManagerisundefinedbefore the first measurement or after the list is hidden/dirtied, callingrecordInteraction()causes an unhandled exception that crashes the hybrid app.What alternative solutions did you explore? (Optional)
Catching the error with
try / catchinsideExternalScrollFlashListTable.tsxonly protects the synthetic scroll path of MoneyRequestReportView, but does not protect native FlashLists throughout the app (such asWorkspaces_ListorSearchRouter). Fixing this at the FlashList patch level protects all lists globally.What changes do you think we should make in order to solve the problem?
Add a patch in
patches/@shopify/flash-list/(or update patch002/ create patch017+fix-record-interaction-without-layout.patch):In
node_modules/@shopify/flash-list/dist/recyclerview/RecyclerViewManager.js(and src counterpart):Guard
recordInteraction()usinghasLayout():In
node_modules/@shopify/flash-list/dist/recyclerview/RecyclerView.js(and src counterpart):Also add a safeguard in the
onScrollcallback:Update
patches/@shopify/flash-list/details.mddocumenting this safeguard alongside the existingscrollToIndexguard in patch002.What automated tests can you add to prevent this from re-occurring?
Add a test in
tests/unit/FlashListTest.tsx(or a unit test verifyingRecyclerViewManager) that triggersrecordInteraction()on a newly instantiatedRecyclerViewManagerwhoselayoutManageris undefined, verifying that it safely early-returns without throwing"LayoutManager is not initialized, window size is unavailable".melvin-bot commentedon Oct 21, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
magomedta commentedon Oct 21, 2026
Contributor details
Your Expensify account email: [email protected]
Upwork Profile Link: N/A (Expensify NewDot Direct Settlement)
Proposal
Please re-state the problem that we are trying to solve in this issue.
On iOS HybridApp (Hermes / Fabric), during screen transitions or before/after layout computation (such as on
Workspaces_Listor back-navigation teardown), native scroll events dispatch to FlashList and crash the app with a fatal error:Error: LayoutManager is not initialized, window size is unavailablethrown from
@shopify/flash-list/dist/recyclerview/RecyclerViewManager.js->getWindowSize(), called viaitemViewabilityManager.recordInteraction().What is the root cause of that problem?
When a native scroll event arrives from iOS Fabric,
RecyclerView.jsexecutes itsonScrollhandler:Notice the immediate architectural disparity between the two calls:
computeItemViewability()(line 186) safely guards itself against uninitialized layout:recordInteraction()(line 184) has nohasLayout()guard:ViewabilityManager.js:16-29,recordInteraction()immediately mutates state:updateViewableItems():ViewabilityManager.jsline 29 explicitly assumes thatthis.rvManager.getWindowSize()can returnundefinedwhen the window size / layout is not yet ready (if (listSize === undefined)).However,
RecyclerViewManager.js:118-123does not returnundefined; it throws unconditionally:ExternalScrollFlashListTable.tsx:113-115),layoutManageris undefined, causinggetWindowSize()to throw an unhandled fatal exception.this.hasInteracted = truebefore verifying layout means that if an interaction occurs prior to layout initialization, the list latches its interaction state prematurely before viewable items can actually be evaluated.What changes do you think we should make in order to solve the problem?
We should patch
@shopify/flash-list(following the exact pattern used in patches002,009, and016):Guard
recordInteractioninnode_modules/@shopify/flash-list/src/recyclerview/RecyclerViewManager.ts&dist/recyclerview/RecyclerViewManager.js:Ensure
recordInteraction()early-returns whenhasLayout()is false, exactly likecomputeItemViewability():This prevents premature latching of
hasInteracted = trueand preserves interaction tracking for the subsequent scroll once layout completes.Guard
updateViewableItemsinnode_modules/@shopify/flash-list/src/recyclerview/viewability/ViewabilityManager.ts&dist/recyclerview/viewability/ViewabilityManager.js:Align the implementation with its intended design (handling uninitialized layout without throwing):
Register patch in
patches/@shopify/flash-list/& updatedetails.md:Add
@shopify+flash-list+2.3.2+017+guard-viewability-record-interaction-layout.patch(or fold intopatches/@shopify/flash-list/), with comprehensive documentation inpatches/@shopify/flash-list/details.mdfollowing the repo's mandatory patch protocol.What alternative solutions did you explore? (Optional)
ExternalScrollFlashListTable.tsx/TableBody.tsx:ExternalScrollFlashListTable.tsx:112already has a localtry/catchfor synthetic scroll events, but native screens (e.g.Workspaces_List,SearchRouter) use native FlashList scrolling where React event dispatches bypass local wrappers. A library-level patch is required to protect all FlashLists across all platforms.getWindowSize()to returnundefined:While
ViewabilityManager.jsline 29 expectsundefined, changinggetWindowSize()return signature affects TypeScript declarations and external consumers ofRecyclerViewManager. Addingif (!this.hasLayout()) return;torecordInteraction()andupdateViewableItems()cleanly stops the error at the source while maintaining strict API type contracts.What specific scenarios should we cover in automated tests?
Add a unit test in
tests/unit/FlashListTest.tsx(orRecyclerViewManagerTest.tsx):RecyclerViewManagerwith mock props andlayoutManager = undefined.recordInteraction().Error: LayoutManager is not initialized, window size is unavailable.layoutManageris provided and initialized, callingrecordInteraction()successfully updates viewability state.melvin-bot commentedon Oct 21, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
linhvovan29546 commentedon Oct 21, 2026
@MelvinBot Please analyze the issue and provide a proposal.
MelvinBot commentedon Oct 21, 2026
Proposal
What is the root cause of that problem?
Our own FlashList patch 002 (skip layout when hidden) causes this. When a list first mounts at 0×0 (a screen that is hidden or not sized yet), the patch skips the first layout. So FlashList never creates its
layoutManager.Upstream FlashList always creates it on the first layout, so upstream never hits this.
Until a real
onLayoutarrives, any native scroll event crashes the list. On iOS, UIKit can send a scroll event with no user touch, for example when it adjusts the content inset under the tab bar. FlashList'sonScrollthen callsrecordInteraction()→updateViewableItems(). That readsgetWindowSize()before it checks whether the list has any viewability listeners, andgetWindowSize()throws.Workspaces_Listhas no viewability callbacks, yet it still crashes because of that check order.Code references
updateLayoutParamscreates the layout manager:@shopify+flash-list+2.3.2+002+skip-layout-when-hidden.patch:9onScrollcallsrecordInteraction()whenever there is noinitialScrollIndex:RecyclerView.tsx:313updateViewableItemscallsgetWindowSize()at L74 and checksshouldListenToVisibleIndicesonly at L75:ViewabilityManager.ts:62-75getWindowSize()throws when there is no layout manager:RecyclerViewManager.ts:172computeItemViewability()→computeVisibleIndices()throws the same way for lists that do use viewability. It has ashouldListenToVisibleIndicescheck but nohasLayout()check:RecyclerViewManager.ts:248layoutManageris never cleared, so only a list that has never had a non-zero layout can crash.recordInteractionalso exits early after its first call, so this can only happen on the first scroll event.scrollToIndexandscrollToOffset. Remove patch to get rid of "LayoutManager is not initialized, window size is unavailable" errors (closed) showed that removing patch 002 stops the throw.mainstill have the same unguarded code. Shopify/flash-list has no issue or PR for this message.What changes do you think we should make in order to solve the problem?
Extend patch 002 so the scroll path handles the "no layout yet" state it creates:
ViewabilityManager.updateViewableItems, return early when there are no viewability listeners orrvManager.hasLayout()is false. Do both checks before callinggetWindowSize(). This fixes every list without viewability callbacks, includingWorkspaces_List.RecyclerViewManager.recordInteraction, return early whenhasLayout()is false. ThenhasInteractedis not set before the list can measure anything, sowaitForInteractionstill works on the next scroll.RecyclerViewManager.computeItemViewability, add ahasLayout()check. This covers lists that useonViewableItemsChanged.patches/@shopify/flash-list/details.md.RecyclerViewManagerwith no layout does not throw fromrecordInteraction()orcomputeItemViewability(), and viewability still fires after the first layout.What alternative solutions did you explore? (Optional)
recordInteraction, as the other proposals suggest. Lists with viewability callbacks would still crash throughcomputeItemViewability.onScroll, so a try/catch in our code cannot catch it.Confidence and what's unverified
The code path is confirmed. The exact iOS trigger is not verified on a device. It is either a UIKit inset adjustment or a tab-mount race on a list first measured at 0×0. Sentry breadcrumbs could confirm it: look for a tab switch or navigation just before the crash, with no
onScrollBeginDrag. The fix works whichever trigger it is.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/104502)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
magomedta commentedon Oct 21, 2026
Hi @linhvovan29546 — a quick note for the review: as detailed in our proposal above, we specifically identified both RecyclerViewManager.recordInteraction() and ViewabilityManager.updateViewableItems() requiring the hasLayout() guards (preventing premature latching of hasInteracted = true and guarding before getWindowSize()). We can also include the defensive computeItemViewability() guard and the unit test in the patch/PR when assigned. Ready to submit the PR immediately upon approval. Thanks!
mountiny commentedon Oct 21, 2026
@linhvovan29546 please choose a proposal. Thank you.
linhvovan29546 commentedon Oct 22, 2026
MelvinBot's proposal LGTM!
🎀 👀 🎀 C+ reviewed
melvin-bot commentedon Oct 22, 2026
Current assignee @mountiny is eligible for the choreEngineerContributorManagement assigner, not assigning anyone new.
mountiny commentedon Oct 22, 2026
@linhvovan29546 thanks! please go ahead