Skip to content

Commit 0f5eead

Browse files
rootclaude
andcommitted
fix(android, tabs): broaden selectedTab fallback to cover applyDayNightUiMode race
The original patch guarded `updateBottomNavigationViewAppearance` and made the lookup helpers null-safe, but field testing surfaced a SECOND crash path that bypasses those guards: IllegalStateException: [RNScreens] No selected tab present at TabsContainer.getSelectedTab(TabsContainer.kt:88) at TabsAppearanceCoordinator.updateTabAppearance(TabsAppearanceCoordinator.kt:21) at TabsContainer.applyDayNightUiMode(TabsContainer.kt:591) at TabsContainer.onAttachedToWindow$lambda$11(TabsContainer.kt:271) at ColorSchemeCoordinator.applyResolvedColorScheme(ColorSchemeCoordinator.kt:108) at ColorSchemeCoordinator.setup$react_native_screens_release(ColorSchemeCoordinator.kt:73) at TabsContainer.onAttachedToWindow(TabsContainer.kt:270) ColorSchemeCoordinator.setup fires the day/night callback as part of the same onAttachedToWindow window where fragment screenKeys haven't flushed. The callback reaches updateTabAppearance → selectedTab on a code path that did not previously touch updateBottomNavigationViewAppearance, so guarding only that method left this crash unmitigated. Guard at the source instead: make the `selectedTab` getter fall back to the first bound fragment when navState.selectedScreenKey does not resolve. Only throw when no usable fragment exists at all — that is the genuine programmer error the original `checkNotNull` was meant to catch. Semantics: - Happy path (fragments bound) — unchanged: getFragmentForScreenKey returns the matching fragment. - Initial-mount race (selectedScreenKey set, no fragment bound yet) — fallback to first bound fragment; one frame of marginally-wrong appearance is far cheaper than a cold-start IllegalStateException that kills the activity. - Genuine empty state (tabsModel has zero entries) — checkNotNull still throws with the same diagnostic message. Verified on Pixel 9 (Android 16) cold-start with the same minimal NativeTabs reproduction as the original patch: previously crashed 100% of the time with 'No selected tab present', now starts cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7337cb2 commit 0f5eead

1 file changed

Lines changed: 12 additions & 1 deletion

File tree

  • android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container

android/src/main/java/com/swmansion/rnscreens/gamma/tabs/container/TabsContainer.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,19 @@ class TabsContainer internal constructor(
8484
internal var rejectStaleNavigationStateUpdates: Boolean = false
8585

8686
internal val selectedTab: TabsScreenFragment
87+
// During the Fabric initial-mount race, no fragment has bound
88+
// its screenKey yet so the strict lookup throws. Fall back to
89+
// the first bound fragment; only throw if tabsModel is truly
90+
// empty (genuine programmer error vs Fabric prop-batching
91+
// ordering). Without this, applyDayNightUiMode →
92+
// updateTabAppearance → selectedTab crashes onAttachedToWindow
93+
// even when other appearance entry points are guarded, because
94+
// ColorSchemeCoordinator invokes the callback on a different
95+
// code path that bypasses the entry guards.
8796
get() =
88-
checkNotNull(getFragmentForScreenKey(navState.selectedScreenKey)) { "[RNScreens] No selected tab present" }
97+
getFragmentForScreenKey(navState.selectedScreenKey)
98+
?: tabsModel.firstOrNull { !it.tabsScreen.screenKey.isNullOrBlank() }
99+
?: checkNotNull(null as TabsScreenFragment?) { "[RNScreens] No selected tab present" }
89100

90101
internal val invalidationFlags = TabsContainerInvalidationFlags()
91102

0 commit comments

Comments
 (0)