From 7a7413821909f09f9d33d95ce36c819cac096e3b Mon Sep 17 00:00:00 2001 From: foxojs Date: Mon, 20 Jul 2026 16:05:53 +0100 Subject: [PATCH 1/2] fix(android): key onNativeLayout guard on layoutHolder so transient startup heights self-heal The layout-change listener guards re-reporting on the OUTER view's size (right - left / bottom - top) but reports the INNER layoutHolder's size to JS. During app startup these can disagree: the outer frame settles at its final size while layoutHolder is still mid-layout at a transient (roughly half-window) height. When the listener fires in that window, the transient layoutHolder height is reported to JS and the outer size is stored in lastReportedSize. Once layoutHolder finishes laying out, the outer size is unchanged, so the guard never allows a re-report: every tab screen stays styled with the transient height for the whole session (content clipped, dead space below; stack screens and absolutely-positioned siblings are unaffected). Observed persistently in production on a Pixel 6a / Android 16. Key the guard on layoutHolder's own dimensions - the same view whose size is reported - so any later correct layout is detected as a change and re-reported. Worst case the wrong height lives for a single frame before the next layout pass corrects it (measured 5 ms in a deterministic reproduction). Regression introduced by #283, which changed the reported size from the outer view to layoutHolder without changing the guard. Co-Authored-By: Claude Fable 5 --- .../src/main/java/com/rcttabview/RCTTabView.kt | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt index 3c3d4bdb..3ec77075 100644 --- a/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt +++ b/packages/react-native-bottom-tabs/android/src/main/java/com/rcttabview/RCTTabView.kt @@ -100,17 +100,23 @@ class ReactBottomNavigationView(context: Context) : LinearLayout(context) { uiModeConfiguration = resources.configuration.uiMode post { - addOnLayoutChangeListener { _, left, top, right, bottom, + addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> - val newWidth = right - left - val newHeight = bottom - top + // Key the change-guard on the same view we report (layoutHolder), not + // on this outer view: during startup the outer frame can settle while + // layoutHolder is still mid-layout, so guarding on the outer size + // latches a transient layoutHolder height for the whole session (tab + // content clipped, dead space below). Guarding on layoutHolder itself + // means any later correct layout re-reports and the view self-heals. + val newWidth = layoutHolder.width + val newHeight = layoutHolder.height // Notify about tab bar height. onTabBarMeasuredListener?.invoke(Utils.convertPixelsToDp(context, bottomNavigation.height).toInt()) if (newWidth != lastReportedSize?.width || newHeight != lastReportedSize?.height) { - val dpWidth = Utils.convertPixelsToDp(context, layoutHolder.width) - val dpHeight = Utils.convertPixelsToDp(context, layoutHolder.height) + val dpWidth = Utils.convertPixelsToDp(context, newWidth) + val dpHeight = Utils.convertPixelsToDp(context, newHeight) onNativeLayoutListener?.invoke(dpWidth, dpHeight) lastReportedSize = Size(newWidth, newHeight) From 37c78de7f9093b8c3d571fa8afe98183b91863a2 Mon Sep 17 00:00:00 2001 From: foxojs Date: Mon, 20 Jul 2026 16:09:16 +0100 Subject: [PATCH 2/2] chore: add changeset Co-Authored-By: Claude Fable 5 --- .changeset/tall-mails-refuse.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tall-mails-refuse.md diff --git a/.changeset/tall-mails-refuse.md b/.changeset/tall-mails-refuse.md new file mode 100644 index 00000000..f26eaf06 --- /dev/null +++ b/.changeset/tall-mails-refuse.md @@ -0,0 +1,5 @@ +--- +"react-native-bottom-tabs": patch +--- + +Fix Android tab screens staying clipped at a transient startup height for the whole session. The onNativeLayout change-guard compared the outer view's size while reporting the inner layoutHolder's size, so a height caught mid-layout during startup was never re-reported. The guard is now keyed on layoutHolder itself, so a later correct layout re-reports and the view self-heals.