feat(Android, Stack v5): add basic support for header#3753
Conversation
There was a problem hiding this comment.
Pull request overview
Adds foundational Android support for Stack v5 headers using Material 3 App Bar, primarily by introducing native header view structure and synchronizing native layout with Yoga via Fabric state.
Changes:
- Adds new Fabric ShadowNode/State/ComponentDescriptor for
RNSStackScreento drive Yoga sizing and content origin offset from native state. - Introduces Android StackScreen header infrastructure (CoordinatorLayout + AppBarLayout variants) and a state proxy to push layout/offset updates into Fabric state.
- Adds an Android-only single-feature test scenario entry for validating header modes (currently static/WIP).
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/fabric/gamma/stack/StackScreenNativeComponent.ts | Marks StackScreen codegen component as interface-only for Fabric. |
| react-native.config.js | Registers RNSStackScreenComponentDescriptor for Android codegen. |
| ios/gamma/stack/screen/RNSStackScreenComponentView.mm | Includes the new StackScreen component descriptor header. |
| common/cpp/react/renderer/components/rnscreens/RNSStackScreenState.h | Introduces StackScreen Fabric state container (Android: frame + content offset). |
| common/cpp/react/renderer/components/rnscreens/RNSStackScreenState.cpp | Implements Android getDynamic() for StackScreen state. |
| common/cpp/react/renderer/components/rnscreens/RNSStackScreenShadowNode.h | Adds StackScreen ShadowNode (Android override for getContentOriginOffset). |
| common/cpp/react/renderer/components/rnscreens/RNSStackScreenShadowNode.cpp | Defines component name and returns content offset from state (Android). |
| common/cpp/react/renderer/components/rnscreens/RNSStackScreenComponentDescriptor.h | Adds descriptor adopt logic to push state-derived size into Yoga (Android). |
| apps/src/tests/single-feature-tests/stack-v5/test-stack-header-modes.tsx | Adds Android-only WIP scenario to exercise header modes/scrolling. |
| apps/src/tests/single-feature-tests/stack-v5/index.ts | Registers the new Stack v5 header modes scenario. |
| android/src/main/jni/rnscreens.h | Exposes RNSStackScreenComponentDescriptor to Android JNI compilation. |
| android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt | Adds helper to resolve theme dimension attributes for Material sizing. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/configuration/StackScreenHeaderType.kt | Adds enum for basic header modes (small/medium/large). |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/configuration/StackScreenHeaderConfigurationProviding.kt | Adds interface describing header configuration inputs on native side. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenScrollingViewBehavior.kt | Adds Coordinator behavior to observe scrolling dependency changes and report header height. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenHeaderCoordinator.kt | Adds coordinator that builds/removes app bar structure and wires behavior + title updates. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenCoordinatorLayout.kt | Adds CoordinatorLayout wrapper to host header + screen wrapper and trigger relayout sync. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenAppBarLayout.kt | Adds Material3 AppBarLayout implementations for small + collapsing (medium/large). |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenViewManager.kt | Stores Fabric StateWrapper on the view so native can push state updates. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenShadowStateProxy.kt | Adds proxy that diffs and pushes frame/offset updates into Fabric state. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenFragment.kt | Wraps StackScreen inside the new CoordinatorLayout root to enable native header layout. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreen.kt | Pushes measured size into Fabric state during layout and exposes state wrapper hook. |
| android/src/main/java/com/swmansion/rnscreens/gamma/stack/host/StackContainer.kt | Switches container base class to FrameLayout, passes context into fragments, adds forced measure/layout pass helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
.../main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenCoordinatorLayout.kt
Show resolved
Hide resolved
android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenFragment.kt
Outdated
Show resolved
Hide resolved
android/src/main/java/com/swmansion/rnscreens/gamma/stack/host/StackContainer.kt
Show resolved
Hide resolved
| require(context.theme.resolveAttribute(attrId, typedValue, true)) { | ||
| "[RNScreens] Unable to resolve Material theme dimension." |
There was a problem hiding this comment.
The require(...) message is too generic to debug theme issues. Including which attribute failed to resolve (e.g. attrId as hex) and/or the current theme would make failures actionable, especially since this will throw at runtime.
| require(context.theme.resolveAttribute(attrId, typedValue, true)) { | |
| "[RNScreens] Unable to resolve Material theme dimension." | |
| val theme = context.theme | |
| require(theme.resolveAttribute(attrId, typedValue, true)) { | |
| "[RNScreens] Unable to resolve Material theme dimension (attrId=0x%08x, theme=%s)".format( | |
| attrId, | |
| theme, | |
| ) |
There was a problem hiding this comment.
I don't think that this is necessary, we need those dimensions from Material, we don't have a reasonable fallback. If something breaks, you can add a breakpoint to investigate.
.../main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenCoordinatorLayout.kt
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 23 out of 23 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
android/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/StackScreenFragment.kt:4
- Unused import
android.content.Contextis left in this file after switching torequireContext(). This will be flagged by Spotless/ktlint (and can fail CI). Remove the unused import.
import android.os.Bundle
import android.view.Gravity
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
android/src/main/java/com/swmansion/rnscreens/gamma/stack/host/StackContainer.kt
Show resolved
Hide resolved
common/cpp/react/renderer/components/rnscreens/RNSStackScreenState.h
Outdated
Show resolved
Hide resolved
...d/src/main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenAppBarLayout.kt
Show resolved
Hide resolved
.../main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenCoordinatorLayout.kt
Show resolved
Hide resolved
.../main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenHeaderCoordinator.kt
Show resolved
Hide resolved
.../main/java/com/swmansion/rnscreens/gamma/stack/screen/header/StackScreenHeaderCoordinator.kt
Show resolved
Hide resolved
common/cpp/react/renderer/components/rnscreens/RNSStackScreenComponentDescriptor.h
Show resolved
Hide resolved
| shadowNode.getState()); | ||
| auto stateData = state->getData(); | ||
|
|
||
| if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { |
There was a problem hiding this comment.
I'm not sure here. We want to setSize when we get data from native. If native supplies incorrect values, I think it's better that we apply them and we discover a bug rather than rely on previous value being valid.
There was a problem hiding this comment.
I'm wondering whether we should apply them or throw, because of an incorrect state
There was a problem hiding this comment.
We would need to also update other components (Stack v4, Tabs Bottom Accessory, Split Screen) as they all use the same pattern I guess.
There was a problem hiding this comment.
so maybe just let's make the ticket for that
There was a problem hiding this comment.
There was a problem hiding this comment.
btw I wouldn't use = for floats
It doesn't use any CoordinatorLayout features.
I'm not really sure which context should we use. Might want to revisit it later.
TODO: handle layout of CoordinatorLayout, now it's added in a random place.
I did not apply any changes to color - header won't be transparent but will have correct layout for transparent header.
1536e9c to
8b00e5b
Compare
kmichalikk
left a comment
There was a problem hiding this comment.
Few nitpicks, could be done in the followup
| @@ -0,0 +1,8 @@ | |||
| package com.swmansion.rnscreens.gamma.stack.screen.header.configuration | |||
|
|
|||
| internal interface StackScreenHeaderConfigurationProviding { | |||
There was a problem hiding this comment.
what is the benefit of using interface here instead of, for instance, a simple dataclass? I can see later objects artificially created that implement the interface inline. I'm not sure I like it, though I don't know, maybe this is an idiomatic way?
There was a problem hiding this comment.
Arguments of such dataclass should not have -Providing then (some cases below in this PR)
There was a problem hiding this comment.
StackHeaderConfig (native React component) will implement this interface. The object is for debugging in this PR.
| */ | ||
| private fun stackContainerOrNull(): StackContainer? = this.parent as StackContainer? | ||
|
|
||
| // TODO: do we need to rely on parent here? |
There was a problem hiding this comment.
Let's settle this before merging
| appBarLayout: StackScreenAppBarLayout, | ||
| title: String, | ||
| ) { | ||
| // TODO: diffing mechanism? |
There was a problem hiding this comment.
Let's settle on something before merging
| ) { | ||
| val stackScreenWrapper = coordinatorLayout.stackScreenWrapper | ||
| val params = stackScreenWrapper.layoutParams as CoordinatorLayout.LayoutParams | ||
| val needsBehavior = appBarLayout != null && !config.isTransparent && !config.isHidden |
There was a problem hiding this comment.
nit: needsBehavior is vague to me, maybe something more descriptive? hasBehavior is okay if it implies "has any behavior set"
There was a problem hiding this comment.
Won't be needed after follow-up PR: #3796.
| } | ||
|
|
||
| companion object { | ||
| private const val DELTA = 0.9f |
| shadowNode.getState()); | ||
| auto stateData = state->getData(); | ||
|
|
||
| if (stateData.frameSize.width != 0 && stateData.frameSize.height != 0) { |
There was a problem hiding this comment.
btw I wouldn't use = for floats
Description
Adds basic support for the header on Android in Stack v5 using Material 3 App Bar.
This PR focuses on creating the class structure and handling layout synchronization between native side and Yoga.
There is no configuration exposed to JS, on the native side
StackScreenHeaderConfigurationProvidingexposes basic properties:headerMode: can be one ofsmall,medium,largetitleisHiddenisTransparent- please note here that this does not change the color of the header to transparent but only the layout. Changing appearance will be handled in separate PRs (then setting this or similar prop will override background color). Also note that this option currently might work incorrectly withliftOnScrollenabled inAppBarLayout. This will be handled in the future PRs.Closes https://github.com/software-mansion/react-native-screens-labs/issues/892.
Closes https://github.com/software-mansion/react-native-screens-labs/issues/903.
Changes
StackScreenBefore & after - visual documentation
header_small_no_scroll.mp4
header_large_scroll.mp4
liftOnScrolldisabled)header_small_no_scroll_transparent.mp4
header_large_scroll_dynamic_hide.mp4
Test plan
Run
single-feature-tests/test-stack-header-modes.tsx. For testing, you can change properties, currently hard-coded inStackScreenCoordinatorLayoutand scroll flags/liftOnScrollinStackScreenAppBarLayout.Checklist