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
[$250] [Sentry: APP-N5H] HybridApp Android: native tab bar crashes with more than 6 items #104505
Last seen: 2026-10-20 (ongoing, Sentry marks it as escalating)
Platform: Android HybridApp
App version(s): 9.5.13-0 (31 users) and 9.5.13-1 (4 users), plus 1 user each on 9.5.9-4, 9.5.10-0, 9.5.11-0, 9.5.13-3 and 9.5.14-0
Affected route(s) (parameterized): native screen; the sample event was on Settings_Troubleshoot
Mechanism: UncaughtExceptionHandler, level fatal
Most users were on 9.5.13-0, a staging build. 9.5.13-6 is now rolling out to production on Android, so this may grow.
Stack trace (top frames, first-party only)
com.swmansion.rnscreens.ScreenStack.onUpdate (ScreenStack.kt:323)
com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.onAttachedToWindow (TabsContainer.kt:311)
com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.flushPendingUpdates (TabsContainer.kt:212)
com.swmansion.rnscreens.gamma.tabs.container.TabsContainer.updateNavigationMenuStructure (TabsContainer.kt:545)
com.swmansion.rnscreens.gamma.tabs.container.MenuHelpersKt.getOrCreateMenuItemForFragment (MenuHelpers.kt:8)
androidx.appcompat.view.menu.MenuBuilder.add
IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6
Suspected cause
The native tab container adds a seventh item to the Android bottom navigation menu, which supports at most 6. It happens when a screen stack attaches the tab container again, so the tab menu items may be added again instead of reused.
Reproduction
Unknown. See the Sentry events linked from the Sentry issue.
changed the title [-][Sentry: APP-N5H] HybridApp Android: native tab bar crashes with more than 6 items[/-][+][$175] [Sentry: APP-N5H] HybridApp Android: native tab bar crashes with more than 6 items[/+]on Oct 20, 2026
Please re-state the problem that we are trying to solve in this issue.
HybridApp on Android crashes with IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6 when a screen stack re-attaches the native tab container or when more than 6 navigation items are added.
What is the root cause of that problem?
Android's BottomNavigationView / CustomBottomNavigationView enforces a hard limit of maximum 6 items (MAX_ITEM_COUNT = 6).
In PR #101339 and patch @react-navigation+bottom-tabs+7.19.2+003+hidden-tab-items.patch, the app registers 6 tab routes:
HOME, REPORTS_SPLIT_NAVIGATOR, SEARCH_FULLSCREEN_NAVIGATOR, INSIGHTS, WORKSPACE_NAVIGATOR, SETTINGS_SPLIT_NAVIGATOR.
Although getTabWithoutBarItem designates one route as abBarItemHidden: true to stay at 5 items, when a screen stack re-attaches (ScreenStack.kt:onUpdate -> TabsContainer.kt:onAttachedToWindow -> flushPendingUpdates -> updateNavigationMenuStructure), menu items from the previous fragment attachment can persist or re-accumulate without clearing or recycling if lushPendingUpdates runs repeatedly during stack transitions. Adding the new items pushes the menu size to 7 (or above), throwing IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.
What changes do you think we should make in order to solve the problem?
In TabsContainer.kt (or via patch to
eact-native-screens), before calling updateNavigationMenuStructure / getOrCreateMenuItemForFragment, ensure menu.clear() is called or check that existing menu items matching the fragment tabs are reused/updated rather than re-added.
In @react-navigation+bottom-tabs+7.19.2+003+hidden-tab-items.patch, strictly guard that routes marked with abBarItemHidden: true are never passed as native tab fragments to RNScreens under any transition state.
Automated Tests
Add a test in ScreenStackTest / navigation tests that mounts the native tab navigator, performs stack pushes and pops (such as opening Settings_Troubleshoot), and verifies that the menu items count in BottomNavigationView never exceeds 5.
Please re-state the problem that we are trying to solve in this issue.
On Android HybridApp, navigating between screens (e.g., visiting Settings_Troubleshoot or during screen transitions inside a ScreenStack) crashes fatally with: IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6
thrown from:
In react-native-screens (gamma tabs on Android), TabsContainer manages bottom navigation tabs using CustomBottomNavigationView (based on Android Material's BottomNavigationView). Android's bottom navigation menu enforces a strict hardware constraint: MAX_ITEM_COUNT = 6 (or 5 standard, 6 custom). Adding a 7th item to MenuBuilder throws IllegalArgumentException.
In ScreenStack.kt:286, when screen fragments are updated or re-appended in stack order, the fragment containing the tab bar is detached and re-attached to the window (transaction.remove(wrapper.fragment) followed by transaction.add(...)).
When TabsContainer.onAttachedToWindow() executes, it calls flushPendingUpdates() -> updateNavigationMenuStructure().
In updateNavigationMenuStructure(), TabsContainer iterates through its tab fragments and calls getOrCreateMenuItemForFragment(fragment) which delegates to bottomNavigationView.menu.add(...).
However, updateNavigationMenuStructure() does not clear or synchronize the existing bottomNavigationView.menu prior to re-populating items. Because items from the initial layout pass are already present in the menu, re-adding the tabs appends duplicate menu items to the MenuBuilder. As soon as menu.size() reaches 7 (e.g. 4 tabs added twice = 8 items), Android throws IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.
What alternative solutions did you explore? (Optional)
None, clearing or diffing existing menu items in TabsContainer.updateNavigationMenuStructure before adding items is the cleanest, idiomatic solution.
What changes do you think we should make in order to solve the problem?
Add a patch in patches/react-native-screens/ (or update existing gamma patches, e.g. react-native-screens+4.28.0+006+clear-tab-menu-before-update.patch):
In node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt:
Before populating fragments in updateNavigationMenuStructure(), clear the existing menu:
--- a/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt+++ b/node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt@@ -540,6 +540,7 @@ class TabsContainer(
}
private fun updateNavigationMenuStructure() {
+ bottomNavigationView.menu.clear()
fragments.forEachIndexed { index, fragment ->
val menuItem = getOrCreateMenuItemForFragment(fragment)
// configure item...
Or in MenuHelpers.kt:
Ensure that getOrCreateMenuItemForFragment checks bottomNavigationView.menu.findItem(fragment.id) instead of unconditionally calling menu.add(...).
What automated tests can you add to prevent this from re-occurring?
Add an Android instrumented/unit test for TabsContainer verifying that calling onAttachedToWindow() and flushPendingUpdates() repeatedly on a TabsContainer with 4-5 tabs never causes bottomNavigationView.menu.size() to exceed the fragment count or throw IllegalArgumentException.
There are two interconnected defects in react-native-screens gamma tabs on Android:
Child Removal Failure in Fabric (TabsHostViewManager):
In React Native Fabric, ViewGroupManager manages view additions and removals by inspecting child views via getChildCount(parent) and getChildAt(parent, index).
In TabsHostViewManager.kt, TabsHost does not attach TabsScreen views directly as Android ViewGroup children (they are managed in an internal renderedScreens list and encapsulated in TabsScreenFragment inside TabsContainer.tabsModel).
However, TabsHostViewManager does not override getChildCount or getChildAt. Consequently, React Native defaults to parent.childCount (which only sees the root container, count = 1).
When Fabric attempts to remove an inactive or toggled tab child at index > 0, getChildAt(parent, index) returns out of bounds or mismatches, causing Fabric to consider the view "already removed from parent" and skip calling removeViewAt / removeView.
As a result, TabsHost.unmountReactSubviewAt and TabsContainer.removeTabsScreenAt are never called for the old tab. When the tab or replacement screen is re-mounted, TabsHostViewManager.addView calls mountReactSubviewAt -> container.addTabsScreenAt, appending another fragment into tabsModel.
Every tab transition or beta re-render leaks stale fragments into tabsModel, causing tabsModel.size to grow beyond 5 to 7 or more.
Uncapped Menu Population against Hardware Limits in updateNavigationMenuStructure():
In TabsContainer.kt:532-547:
privatefunupdateNavigationMenuStructure() {
val menu = bottomNavigationView.menu
val isMenuInSync =
menu.size == tabsModel.size &&
tabsModel.withIndex().all { (index, fragment) ->
menu.getItem(index).itemId == fragment.menuItemId
}
if (isMenuInSync) return
menu.clear()
tabsModel.forEach { fragment ->
menu.getOrCreateMenuItemForFragment(fragment)
}
}
Notice that menu.clear() on line 543 does clear bottomNavigationView.menu. However, tabsModel.forEach iterates over all items in tabsModel without checking bottomNavigationView.maxItemCount (MAX_ITEM_COUNT = 6 in CustomBottomNavigationView / Android Material NavigationBarMenu).
In TabsHost.kt:76, mountReactSubviewAt only checks index < bottomNavigationViewMaxItemCount, never checking whether renderedScreens.size >= bottomNavigationViewMaxItemCount or whether the TabsScreen is already mounted.
When ScreenStack.onUpdate executes (e.g. re-attaching screens in stack order via transaction.commitNowAllowingStateLoss()), TabsContainer.onAttachedToWindow calls flushPendingUpdates() -> updateNavigationMenuStructure(). Because tabsModel accumulated 7 or more fragments, tabsModel.forEach attempts to add the 7th item into bottomNavigationView.menu.
NavigationBarMenu.addInternal detects size() + 1 > maxItemCount (7 > 6) and throws IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.
What alternative solutions did you explore? (Optional)
Attempting to only cap at the JS layer (NativeBottomTabView.native.js) is insufficient because Fabric view re-ordering and remount cycles on Android will still leak native fragments inside TabsHost.renderedScreens and TabsContainer.tabsModel if TabsHostViewManager cannot report its children. Both the view manager lifecycle and native container menu bounds must be secured.
What changes do you think we should make in order to solve the problem?
Add a patch in patches/react-native-screens/react-native-screens+4.28.0+006+fix-tabs-host-child-lifecycle-and-menu-limit.patch:
In TabsHostViewManager.kt:
Override getChildCount and getChildAt to reflect renderedScreens so Fabric can successfully unmount child screens:
In updateNavigationMenuStructure(), bound the iteration strictly to bottomNavigationView.maxItemCount:
privatefunupdateNavigationMenuStructure() {
val menu = bottomNavigationView.menu
val isMenuInSync =
menu.size == tabsModel.size &&
tabsModel.withIndex().all { (index, fragment) ->
menu.getItem(index).itemId == fragment.menuItemId
}
if (isMenuInSync) {
return
}
menu.clear()
val maxItems = bottomNavigationView.maxItemCount
tabsModel.take(maxItems).forEach { fragment ->
menu.getOrCreateMenuItemForFragment(fragment)
}
}
What automated tests can you add to prevent this from re-occurring?
In Android unit/integration tests (TabsContainerTest / TabsHostTest), verify that repeatedly mounting, unmounting, and re-mounting tabs on TabsHost with Fabric view manager dispatches:
Maintains renderedScreens.size == tabsModel.size.
Never exceeds bottomNavigationView.maxItemCount.
Never throws IllegalArgumentException during onAttachedToWindow() and flushPendingUpdates().
In tests/unit/NativeBottomTabViewTest.tsx, add test cases simulating rapid navigation transitions across tab routes and beta toggles to ensure the number of native Tabs.Screen children mounted to Tabs.Host never exceeds 5.
The App has six tab routes; the native bar shows five. The tab without a bar item gets no Tabs.Screen, so when that tab changes (the Insights beta loading after launch or being toggled), React removes one Tabs.Screen from the native host and inserts another. Before #104238 a re-keyed focused tab route (markFocusedTabRouteForRemount) did the same; both triggers were live in 9.5.13-0:
On Android the removal never reaches react-native-screens. TabsHost keeps its tabs in renderedScreens and hands them to TabsContainer as fragments, so its only real Android child is the container. TabsHostViewManager implements removeViewAt but not getChildAt/getChildCount:
Fabric checks the child with getChildAt before removing it. The default reads the raw Android children, does not find the tab, logs view already removed from parent! and returns, so TabsHost.unmountReactSubviewAt, which drops the fragment from tabsModel, never runs. Inserts have no such check:
Each remove + insert leaves one stale fragment. updateNavigationMenuStructure clears the menu and adds one item per tabsModel entry, so the second change makes 7 and MenuBuilder.add throws. While a page covers the tabs the container is detached and the flush waits for onAttachedToWindow, which is why the stack starts in ScreenStack.onUpdate:
Main now avoids both triggers in JS: the route-name tab key from #104238 (staging 9.5.13-1) and a host remount when the hidden tab changes from #104368 (staging 9.5.16-0, not on production yet). The Android removal itself is unchanged, as the patch 004 note says:
- Reason: Identifies each native tab by its route name instead of its route key. The App gives the focused tab route a new key on purpose when it pre-inserts a screen under the RHP (`markFocusedTabRouteForRemount` in `GetStateForActionHandlers.ts`, #90985), so the split navigator remounts without a push-transition flash. Keyed by route key, that new key replaced the whole `Tabs.Screen`. Android never removes the old one: `TabsHostViewManager` does not override `getChildAt`/`getChildCount`, so Fabric's `removeViewAt` finds no child at that index ("view already removed from parent!") and skips `TabsHost.unmountReactSubviewAt`, while the insert goes through. The bar then shows the tab twice, and the stale copy opens a blank page. With this patch the native tab (`key`, `screenKey`, `navStateRequest.selectedScreenKey` and the selection events) uses the route name, which stays the same, and only the tab's content is keyed by the route key, so a re-keyed route still remounts its content inside the same native tab. **Remove this patch** once react-native-screens removes Android tabs correctly and React Navigation's native tab view no longer recreates a tab when its route key changes.
- Upstream PR/issue: not reported yet. The Android removal bug belongs to react-native-screens (`TabsHostViewManager`).
Back to the tabs: Maximum number of items supported by CustomBottomNavigationView is 6 from MenuHelpers.kt:8 ← TabsContainer.kt:545 ← :212 ← onAttachedToWindow :311 ← ScreenStack.onUpdate (ScreenStack.kt:323), the APP-N5H frames.
On unchanged main the toggles do not crash, but logcat still shows every removal skipped.
What changes do you think we should make in order to solve the problem?
Add react-native-screens+4.28.0+007 so TabsHostViewManager reports renderedScreens to Fabric, as the gamma stack host already does:
- private val renderedScreens: ArrayList<TabsScreen> = arrayListOf()+ internal val renderedScreens: ArrayList<TabsScreen> = arrayListOf()
...
+ override fun getChildAt(parent: TabsHost, index: Int): View? = parent.renderedScreens.getOrNull(index)+ override fun getChildCount(parent: TabsHost): Int = parent.renderedScreens.size
Fabric then reaches unmountReactSubviewAt, and tabsModel matches what JS renders on every remove path. I'd keep the remount and patch 004: TabsContainer.selectedTab is a checkNotNull lookup, so removing the selected tab from a live host still needs them. I have not built the patched APK yet.
What alternative solutions did you explore? (Optional)
Stop adding menu items at 6 in getOrCreateMenuItemForFragment: hides the crash but leaves the stale tab in tabsModel.
The App has six routes but renders five native tabs: the hidden-tab patch omits Tabs.Screen for the hidden route. Earlier builds replaced native tabs when a focused route received a new key or the Insights beta changed which tab was hidden. Current main mitigates these triggers with [stable native tab identity](
- Reason: Identifies each native tab by its route name instead of its route key. The App gives the focused tab route a new key on purpose when it pre-inserts a screen under the RHP (`markFocusedTabRouteForRemount` in `GetStateForActionHandlers.ts`, #90985), so the split navigator remounts without a push-transition flash. Keyed by route key, that new key replaced the whole `Tabs.Screen`. Android never removes the old one: `TabsHostViewManager` does not override `getChildAt`/`getChildCount`, so Fabric's `removeViewAt` finds no child at that index ("view already removed from parent!") and skips `TabsHost.unmountReactSubviewAt`, while the insert goes through. The bar then shows the tab twice, and the stale copy opens a blank page. With this patch the native tab (`key`, `screenKey`, `navStateRequest.selectedScreenKey` and the selection events) uses the route name, which stays the same, and only the tab's content is keyed by the route key, so a re-keyed route still remounts its content inside the same native tab. **Remove this patch** once react-native-screens removes Android tabs correctly and React Navigation's native tab view no longer recreates a tab when its route key changes.
- Upstream PR/issue: not reported yet. The Android removal bug belongs to react-native-screens (`TabsHostViewManager`).
Validate the native boundary with repeated non-selected tab replacements while the host survives, including replacements while detached followed by reattachment. Assert that removed tags disappear and the model/menu retain the expected count. Also verify Android HybridApp expense creation and Insights-beta changes with the existing mitigations retained; mocked JavaScript tab tests cannot exercise Fabric’s removal check.
What alternative solutions did you explore? (Optional)
Clearing the menu is already implemented. Capping menu population would leave stale tabs in the model, while rejecting further insertions would merely move the crash earlier. Correcting child reporting addresses the failed removal itself.
The stack trace shows a seventh native menu entry being added despite Android's six-item limit. A duplicate during tab-container reattachment is plausible but not yet reproduced.
What changes do you think we should make in order to solve the problem?
Instrument tab IDs and native menu counts during attach/update; determine duplicate insertion versus seven distinct tabs, then reconcile entries correctly instead of suppressing the exception.
What alternative solutions did you explore? (Optional)
Avoid a UI-only workaround that masks the symptom without fixing the state or lifecycle problem.
Validation: Test repeated attach/detach and tab switching, including Settings_Troubleshoot.
@mananjadhav I'm available if you need a contributor for this issue. I'll wait for approval before opening a PR.
Activity
melvin-bot commentedon Oct 20, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mananjadhav (
External)[-][Sentry: APP-N5H] HybridApp Android: native tab bar crashes with more than 6 items[/-][+][$175] [Sentry: APP-N5H] HybridApp Android: native tab bar crashes with more than 6 items[/+]melvin-bot commentedon Oct 20, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022112637848604843209
RajGahoi commentedon Oct 20, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
HybridApp on Android crashes with IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6 when a screen stack re-attaches the native tab container or when more than 6 navigation items are added.
What is the root cause of that problem?
Android's BottomNavigationView / CustomBottomNavigationView enforces a hard limit of maximum 6 items (MAX_ITEM_COUNT = 6).
In PR #101339 and patch @react-navigation+bottom-tabs+7.19.2+003+hidden-tab-items.patch, the app registers 6 tab routes:
HOME, REPORTS_SPLIT_NAVIGATOR, SEARCH_FULLSCREEN_NAVIGATOR, INSIGHTS, WORKSPACE_NAVIGATOR, SETTINGS_SPLIT_NAVIGATOR.
Although getTabWithoutBarItem designates one route as abBarItemHidden: true to stay at 5 items, when a screen stack re-attaches (ScreenStack.kt:onUpdate -> TabsContainer.kt:onAttachedToWindow -> flushPendingUpdates -> updateNavigationMenuStructure), menu items from the previous fragment attachment can persist or re-accumulate without clearing or recycling if lushPendingUpdates runs repeatedly during stack transitions. Adding the new items pushes the menu size to 7 (or above), throwing IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.
What changes do you think we should make in order to solve the problem?
eact-native-screens), before calling updateNavigationMenuStructure / getOrCreateMenuItemForFragment, ensure menu.clear() is called or check that existing menu items matching the fragment tabs are reused/updated rather than re-added.
Automated Tests
Add a test in ScreenStackTest / navigation tests that mounts the native tab navigator, performs stack pushes and pops (such as opening Settings_Troubleshoot), and verifies that the menu items count in BottomNavigationView never exceeds 5.
Contributor details
Your Expensify account email: [email protected]
RajGahoi commentedon Oct 20, 2026
Contributor details update:
Upwork Profile Link: https://www.upwork.com/freelancers/~014898525b9c53fc32
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 Android HybridApp, navigating between screens (e.g., visiting
Settings_Troubleshootor during screen transitions inside a ScreenStack) crashes fatally with:IllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6thrown from:
What is the root cause of that problem?
react-native-screens(gamma tabs on Android),TabsContainermanages bottom navigation tabs usingCustomBottomNavigationView(based on Android Material'sBottomNavigationView). Android's bottom navigation menu enforces a strict hardware constraint:MAX_ITEM_COUNT = 6(or 5 standard, 6 custom). Adding a 7th item toMenuBuilderthrowsIllegalArgumentException.ScreenStack.kt:286, when screen fragments are updated or re-appended in stack order, the fragment containing the tab bar is detached and re-attached to the window (transaction.remove(wrapper.fragment)followed bytransaction.add(...)).TabsContainer.onAttachedToWindow()executes, it callsflushPendingUpdates()->updateNavigationMenuStructure().updateNavigationMenuStructure(),TabsContaineriterates through its tab fragments and callsgetOrCreateMenuItemForFragment(fragment)which delegates tobottomNavigationView.menu.add(...).updateNavigationMenuStructure()does not clear or synchronize the existingbottomNavigationView.menuprior to re-populating items. Because items from the initial layout pass are already present in the menu, re-adding the tabs appends duplicate menu items to theMenuBuilder. As soon asmenu.size()reaches 7 (e.g. 4 tabs added twice = 8 items), Android throwsIllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.What alternative solutions did you explore? (Optional)
None, clearing or diffing existing menu items in
TabsContainer.updateNavigationMenuStructurebefore adding items is the cleanest, idiomatic solution.What changes do you think we should make in order to solve the problem?
Add a patch in
patches/react-native-screens/(or update existing gamma patches, e.g.react-native-screens+4.28.0+006+clear-tab-menu-before-update.patch):In
node_modules/react-native-screens/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt:Before populating fragments in
updateNavigationMenuStructure(), clear the existing menu:Or in
MenuHelpers.kt:Ensure that
getOrCreateMenuItemForFragmentchecksbottomNavigationView.menu.findItem(fragment.id)instead of unconditionally callingmenu.add(...).What automated tests can you add to prevent this from re-occurring?
Add an Android instrumented/unit test for
TabsContainerverifying that callingonAttachedToWindow()andflushPendingUpdates()repeatedly on aTabsContainerwith 4-5 tabs never causesbottomNavigationView.menu.size()to exceed the fragment count or throwIllegalArgumentException.melvin-bot commentedon Oct 21, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
sijo0218 commentedon Oct 21, 2026
Contributor details
Your Expensify account email: [email protected]
Upwork Profile Link: https://www.upwork.com/freelancers/~011091133bbb09cd1c
Proposal
Please re-state the problem that we are trying to solve in this issue.
On Android HybridApp, navigating between screens (e.g. visiting sub-pages or during screen transitions in a
ScreenStack) fatal crashes with:thrown from:
What is the root cause of that problem?
There are two interconnected defects in
react-native-screensgamma tabs on Android:Child Removal Failure in Fabric (
TabsHostViewManager):ViewGroupManagermanages view additions and removals by inspecting child views viagetChildCount(parent)andgetChildAt(parent, index).TabsHostViewManager.kt,TabsHostdoes not attachTabsScreenviews directly as AndroidViewGroupchildren (they are managed in an internalrenderedScreenslist and encapsulated inTabsScreenFragmentinsideTabsContainer.tabsModel).TabsHostViewManagerdoes not overridegetChildCountorgetChildAt. Consequently, React Native defaults toparent.childCount(which only sees the rootcontainer, count = 1).index > 0,getChildAt(parent, index)returns out of bounds or mismatches, causing Fabric to consider the view "already removed from parent" and skip callingremoveViewAt/removeView.TabsHost.unmountReactSubviewAtandTabsContainer.removeTabsScreenAtare never called for the old tab. When the tab or replacement screen is re-mounted,TabsHostViewManager.addViewcallsmountReactSubviewAt->container.addTabsScreenAt, appending another fragment intotabsModel.tabsModel, causingtabsModel.sizeto grow beyond 5 to 7 or more.Uncapped Menu Population against Hardware Limits in
updateNavigationMenuStructure():TabsContainer.kt:532-547:menu.clear()on line 543 does clearbottomNavigationView.menu. However,tabsModel.forEachiterates over all items intabsModelwithout checkingbottomNavigationView.maxItemCount(MAX_ITEM_COUNT = 6inCustomBottomNavigationView/ Android MaterialNavigationBarMenu).TabsHost.kt:76,mountReactSubviewAtonly checksindex < bottomNavigationViewMaxItemCount, never checking whetherrenderedScreens.size >= bottomNavigationViewMaxItemCountor whether theTabsScreenis already mounted.ScreenStack.onUpdateexecutes (e.g. re-attaching screens in stack order viatransaction.commitNowAllowingStateLoss()),TabsContainer.onAttachedToWindowcallsflushPendingUpdates()->updateNavigationMenuStructure(). BecausetabsModelaccumulated 7 or more fragments,tabsModel.forEachattempts to add the 7th item intobottomNavigationView.menu.NavigationBarMenu.addInternaldetectssize() + 1 > maxItemCount(7 > 6) and throwsIllegalArgumentException: Maximum number of items supported by CustomBottomNavigationView is 6.What alternative solutions did you explore? (Optional)
Attempting to only cap at the JS layer (
NativeBottomTabView.native.js) is insufficient because Fabric view re-ordering and remount cycles on Android will still leak native fragments insideTabsHost.renderedScreensandTabsContainer.tabsModelifTabsHostViewManagercannot report its children. Both the view manager lifecycle and native container menu bounds must be secured.What changes do you think we should make in order to solve the problem?
Add a patch in
patches/react-native-screens/react-native-screens+4.28.0+006+fix-tabs-host-child-lifecycle-and-menu-limit.patch:In
TabsHostViewManager.kt:Override
getChildCountandgetChildAtto reflectrenderedScreensso Fabric can successfully unmount child screens:In
TabsHost.kt:Expose
renderedScreensCountandgetRenderedScreenAt(index):In
mountReactSubviewAt, guard against duplicate insertion and ensurerenderedScreensdeduplicates any existing entry for the same screen:In
TabsContainer.kt:In
addTabsScreenAt, deduplicate matching screens:In
updateNavigationMenuStructure(), bound the iteration strictly tobottomNavigationView.maxItemCount:What automated tests can you add to prevent this from re-occurring?
TabsContainerTest/TabsHostTest), verify that repeatedly mounting, unmounting, and re-mounting tabs onTabsHostwith Fabric view manager dispatches:renderedScreens.size == tabsModel.size.bottomNavigationView.maxItemCount.IllegalArgumentExceptionduringonAttachedToWindow()andflushPendingUpdates().tests/unit/NativeBottomTabViewTest.tsx, add test cases simulating rapid navigation transitions across tab routes and beta toggles to ensure the number of nativeTabs.Screenchildren mounted toTabs.Hostnever exceeds 5.melvin-bot commentedon Oct 21, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
11 remaining items
melvin-bot commentedon Oct 23, 2026
Current assignee @mountiny is eligible for the choreEngineerContributorManagement assigner, not assigning anyone new.
yusufdeveloper2903 commentedon Oct 23, 2026
Proposal
What is the root cause of that problem?
The App has six tab routes; the native bar shows five. The tab without a bar item gets no
Tabs.Screen, so when that tab changes (the Insights beta loading after launch or being toggled), React removes oneTabs.Screenfrom the native host and inserts another. Before #104238 a re-keyed focused tab route (markFocusedTabRouteForRemount) did the same; both triggers were live in 9.5.13-0:App/patches/react-navigation/@react-navigation+bottom-tabs+7.19.2+003+hidden-tab-items.patch
Lines 87 to 92 in de7716f
On Android the removal never reaches react-native-screens.
TabsHostkeeps its tabs inrenderedScreensand hands them toTabsContaineras fragments, so its only real Android child is the container.TabsHostViewManagerimplementsremoveViewAtbut notgetChildAt/getChildCount:https://github.com/software-mansion/react-native-screens/blob/42788c197e817ebfe21c0f5d36e0b0e02df4950e/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt#L50-L59
Fabric checks the child with
getChildAtbefore removing it. The default reads the raw Android children, does not find the tab, logsview already removed from parent!and returns, soTabsHost.unmountReactSubviewAt, which drops the fragment fromtabsModel, never runs. Inserts have no such check:https://github.com/facebook/react-native/blob/72258ddf7b0b795bbafcbeb5930620b6500c7a06/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt#L456-L482
Each remove + insert leaves one stale fragment.
updateNavigationMenuStructureclears the menu and adds one item pertabsModelentry, so the second change makes 7 andMenuBuilder.addthrows. While a page covers the tabs the container is detached and the flush waits foronAttachedToWindow, which is why the stack starts inScreenStack.onUpdate:https://github.com/software-mansion/react-native-screens/blob/42788c197e817ebfe21c0f5d36e0b0e02df4950e/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt#L532-L547
Main now avoids both triggers in JS: the route-name tab key from #104238 (staging 9.5.13-1) and a host remount when the hidden tab changes from #104368 (staging 9.5.16-0, not on production yet). The Android removal itself is unchanged, as the patch 004 note says:
App/patches/react-navigation/details.md
Lines 82 to 88 in de7716f
App/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/NativeTabLayout.tsx
Lines 19 to 34 in de7716f
I reproduced it on dev Android with that remount
keyremoved (the JS production 9.5.13-6 onward still ships) and watched logcat:insightsPageon:removeViewAt: [2542] -> [2544] @4: view already removed from parent!insightsPageoff:removeViewAt: [6324] -> [2544] @3: view already removed from parent!Maximum number of items supported by CustomBottomNavigationView is 6fromMenuHelpers.kt:8←TabsContainer.kt:545←:212←onAttachedToWindow :311←ScreenStack.onUpdate (ScreenStack.kt:323), the APP-N5H frames.On unchanged main the toggles do not crash, but logcat still shows every removal skipped.
What changes do you think we should make in order to solve the problem?
Add
react-native-screens+4.28.0+007soTabsHostViewManagerreportsrenderedScreensto Fabric, as the gamma stack host already does:https://github.com/software-mansion/react-native-screens/blob/42788c197e817ebfe21c0f5d36e0b0e02df4950e/android/src/main/java/com/swmansion/rnscreens/gamma/stack/host/StackHostViewManager.kt#L52-L57
Fabric then reaches
unmountReactSubviewAt, andtabsModelmatches what JS renders on every remove path. I'd keep the remount and patch 004:TabsContainer.selectedTabis acheckNotNulllookup, so removing the selected tab from a live host still needs them. I have not built the patched APK yet.What alternative solutions did you explore? (Optional)
getOrCreateMenuItemForFragment: hides the crash but leaves the stale tab intabsModel.nabi-ebrahimi commentedon Oct 23, 2026
Proposal
What is the root cause of that problem?
Android skips native tab removals, so stale tabs accumulate in
TabsContainer.tabsModeluntil rebuilding the menu exceeds its six-item limit.TabsHoststores its logical tab children inrenderedScreens; its only direct Android child is the internal container. However,TabsHostViewManagerimplements additions and removals without overridinggetChildAtorgetChildCount. Fabric therefore inspects the wrong children, cannot find the tab’s tag, and returns before callingremoveViewAt. ([TabsHost](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt#L32-L64), [view manager](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHostViewManager.kt#L33-L59), [Fabric removal check](https://github.com/react/react-native/blob/a632f9efe24bac8b3a113c78469948f55bde0f5d/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt#L453-L505))Replacement tabs still reach
mountReactSubviewAt, while the old tabs never reachunmountReactSubviewAt. The insertion guard checks the insertion index, not the accumulated count. Starting with five native tabs, two replacements can therefore leave seven entries. ([Mount/unmount implementation](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/host/TabsHost.kt#L85-L104))updateNavigationMenuStructurealready clears the menu, then adds one item per model entry. The seventh entry causes the exception. Updates wait while the container is detached and resume inonAttachedToWindow, explaining the reattachment path in the reported stack. Reattachment without accumulated stale tabs does not increase the menu count. ([Menu rebuild](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt#L532-L547), [attachment handling](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt#L291-L311))The App has six routes but renders five native tabs: the hidden-tab patch omits
Tabs.Screenfor the hidden route. Earlier builds replaced native tabs when a focused route received a new key or the Insights beta changed which tab was hidden. Current main mitigates these triggers with [stable native tab identity](App/patches/react-navigation/details.md
Lines 82 to 88 in ba97c03
App/src/libs/Navigation/AppNavigator/Navigators/NativeTabNavigator/NativeTabLayout.tsx
Lines 19 to 35 in ba97c03
What changes do you think we should make in order to solve the problem?
Add a sequenced
react-native-screenspatch that exposes read-only accessors forrenderedScreensand overridesTabsHostViewManager.getChildCountandgetChildAtto use them. Follow the existing [gamma stack host implementation](https://github.com/software-mansion/react-native-screens/blob/2381ed8d72fe19185fc8415cb61df7b79bbf4376/android/src/main/java/com/swmansion/rnscreens/gamma/stack/host/StackHostViewManager.kt#L52-L57), including nullable lookup for an invalid index.Fabric can then find the intended child and execute the existing removal, keeping
renderedScreensandtabsModelsynchronized with React.Keep the current stable-identity patch and host-remount behavior. Correct child reporting alone does not make removing the selected tab safe:
selectedTabrequires that tab to remain in the model.Validate the native boundary with repeated non-selected tab replacements while the host survives, including replacements while detached followed by reattachment. Assert that removed tags disappear and the model/menu retain the expected count. Also verify Android HybridApp expense creation and Insights-beta changes with the existing mitigations retained; mocked JavaScript tab tests cannot exercise Fabric’s removal check.
What alternative solutions did you explore? (Optional)
Clearing the menu is already implemented. Capping menu population would leave stale tabs in the model, while rejecting further insertions would merely move the crash earlier. Correcting child reporting addresses the failed removal itself.
bconnnnn commentedon Oct 23, 2026
Proposal
What is the root cause of that problem?
The stack trace shows a seventh native menu entry being added despite Android's six-item limit. A duplicate during tab-container reattachment is plausible but not yet reproduced.
What changes do you think we should make in order to solve the problem?
Instrument tab IDs and native menu counts during attach/update; determine duplicate insertion versus seven distinct tabs, then reconcile entries correctly instead of suppressing the exception.
What alternative solutions did you explore? (Optional)
Avoid a UI-only workaround that masks the symptom without fixing the state or lifecycle problem.
Validation: Test repeated attach/detach and tab switching, including Settings_Troubleshoot.
@mananjadhav I'm available if you need a contributor for this issue. I'll wait for approval before opening a PR.
melvin-bot commentedon Oct 23, 2026
❌ There was an error making the offer to @wildan-m for the Contributor role. The BZ member will need to manually hire the contributor.
wildan-m commentedon Oct 26, 2026
on it
wildan-m commentedon Oct 26, 2026
@mananjadhav the PR is ready #105020
Kabeer099 commentedon Oct 29, 2026