Skip to content

Commit c6ac4a6

Browse files
kkafarUbax
andauthored
fix(Android,Tabs): allow for nesting tabs inside v4 stack (#3084)
## Description Currently only nesting a stack inside tabs is supported & not the other way around. This PR changes that. ## Changes `Screen` now implements `FragmentProviding`, which allows the `TabHost` to use it as a source for child fragment manager. ## Test code and steps to reproduce <details> <summary>Code example</summary> ```javascript import React from 'react'; import { Screen, ScreenStack, enableFreeze } from 'react-native-screens'; import ConfigWrapperContext, { type Configuration, DEFAULT_GLOBAL_CONFIGURATION, } from '../../shared/gamma/containers/bottom-tabs/ConfigWrapperContext'; import { BottomTabsContainer, type TabConfiguration, } from '../../shared/gamma/containers/bottom-tabs/BottomTabsContainer'; import { Tab1, Tab2, Tab3, Tab4 } from './tabs'; import Colors from '../../shared/styling/Colors'; import { StyleSheet } from 'react-native'; enableFreeze(true); const TAB_CONFIGS: TabConfiguration[] = [ { tabScreenProps: { tabKey: 'Tab1', title: 'Tab1', isFocused: true, icon: { sfSymbolName: 'house', }, selectedIcon: { sfSymbolName: 'house.fill', }, iconResourceName: 'sym_call_incoming', // Android specific }, contentViewRenderFn: Tab1, }, { tabScreenProps: { tabKey: 'Tab2', badgeValue: 'NEW', tabBarItemBadgeBackgroundColor: Colors.GreenDark100, tabBarBackgroundColor: Colors.NavyDark140, tabBarItemTitleFontSize: 20, tabBarItemTitleFontStyle: 'italic', tabBarItemTitleFontColor: Colors.RedDark120, tabBarItemTitleFontWeight: 'bold', tabBarItemTitleFontFamily: 'Baskerville', tabBarItemTitlePositionAdjustment: { vertical: 8, }, icon: { templateSource: require('../../../assets/variableIcons/icon.png'), }, selectedIcon: { templateSource: require('../../../assets/variableIcons/icon_fill.png'), }, tabBarItemIconColor: Colors.RedDark120, iconResourceName: 'sym_call_missed', // Android specific title: 'Tab2', }, contentViewRenderFn: Tab2, }, { tabScreenProps: { tabKey: 'Tab3', badgeValue: '2137', tabBarItemBadgeBackgroundColor: Colors.RedDark40, tabBarItemBadgeTextColor: Colors.RedDark120, icon: { imageSource: require('../../../assets/variableIcons/icon.png'), }, selectedIcon: { imageSource: require('../../../assets/variableIcons/icon_fill.png'), }, iconResourceName: 'sym_action_email', // Android specific title: 'Tab3', }, contentViewRenderFn: Tab3, }, { tabScreenProps: { tabKey: 'Tab4', icon: { sfSymbolName: 'rectangle.stack', }, selectedIcon: { sfSymbolName: 'rectangle.stack.fill', }, iconResourceName: 'sym_action_chat', // Android specific title: 'Tab4', badgeValue: '', }, contentViewRenderFn: Tab4, }, ]; function App() { const [config, setConfig] = React.useState<Configuration>( DEFAULT_GLOBAL_CONFIGURATION, ); return ( <ConfigWrapperContext.Provider value={{ config, setConfig, }}> <ScreenStack style={[StyleSheet.absoluteFill]}> <Screen isNativeStack activityState={2} screenId="hello"> <BottomTabsContainer tabConfigs={TAB_CONFIGS} /> </Screen> </ScreenStack> </ConfigWrapperContext.Provider> ); } export default App; ``` </details> ## Checklist - [ ] Ensured that CI passes ## Initial solution See #3077 for problem description & solution suggestion. Co-authored-by: Jakub Tkacz <[email protected]> --------- Co-authored-by: Jakub Tkacz <[email protected]>
1 parent f0d20c0 commit c6ac4a6

File tree

5 files changed

+10
-6
lines changed

5 files changed

+10
-6
lines changed

android/src/main/java/com/swmansion/rnscreens/Screen.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,14 @@ import com.swmansion.rnscreens.events.HeaderHeightChangeEvent
3333
import com.swmansion.rnscreens.events.SheetDetentChangedEvent
3434
import com.swmansion.rnscreens.ext.asScreenStackFragment
3535
import com.swmansion.rnscreens.ext.parentAsViewGroup
36+
import com.swmansion.rnscreens.gamma.common.FragmentProviding
3637

3738
@SuppressLint("ViewConstructor") // Only we construct this view, it is never inflated.
3839
class Screen(
3940
val reactContext: ThemedReactContext,
4041
) : FabricEnabledViewGroup(reactContext),
41-
ScreenContentWrapper.OnLayoutCallback {
42+
ScreenContentWrapper.OnLayoutCallback,
43+
FragmentProviding {
4244
val fragment: Fragment?
4345
get() = fragmentWrapper?.fragment
4446

@@ -120,6 +122,8 @@ class Screen(
120122
layoutParams = WindowManager.LayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION)
121123
}
122124

125+
override fun getAssociatedFragment(): Fragment? = fragment
126+
123127
/**
124128
* ScreenContentWrapper notifies us here on it's layout. It is essential for implementing
125129
* `fitToContents` for formSheets, as this is first entry point where we can acquire

android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ open class ScreenContainer(
176176
private fun setupFragmentManager() {
177177
var parent: ViewParent = this
178178
// We traverse view hierarchy up until we find screen parent or a root view
179-
while (!(parent is ReactRootView || parent is Screen || parent is FragmentProviding) &&
179+
while (!(parent is ReactRootView || parent is FragmentProviding) &&
180180
parent.parent != null
181181
) {
182182
parent = parent.parent
@@ -195,7 +195,7 @@ open class ScreenContainer(
195195
// TODO: We're missing parent-child relationship here between old container & new one
196196
val fragmentManager =
197197
checkNotNull(
198-
parent.getFragment(),
198+
parent.getAssociatedFragment(),
199199
) { "[RNScreens] Parent $parent returned nullish fragment" }.childFragmentManager
200200
setFragmentManager(fragmentManager)
201201
} else {

android/src/main/java/com/swmansion/rnscreens/gamma/common/FragmentProviding.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ import androidx.fragment.app.Fragment
77
* can be used to retrieve child fragment manager for nesting operations.
88
*/
99
interface FragmentProviding {
10-
fun getFragment(): Fragment?
10+
fun getAssociatedFragment(): Fragment?
1111
}

android/src/main/java/com/swmansion/rnscreens/gamma/helpers/FragmentManagerHelper.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object FragmentManagerHelper {
2323
// If parent adheres to FragmentProviding interface it means we are inside a nested fragment structure.
2424
// Otherwise we expect to connect directly with root view and get root fragment manager
2525
if (parent is FragmentProviding) {
26-
return checkNotNull(parent.getFragment()) {
26+
return checkNotNull(parent.getAssociatedFragment()) {
2727
"[RNScreens] Parent fragment providing view $parent returned nullish fragment"
2828
}.childFragmentManager
2929
} else {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class TabScreen(
9393
tabScreenDelegate = WeakReference(delegate)
9494
}
9595

96-
override fun getFragment(): Fragment? = tabScreenDelegate.get()?.getFragmentForTabScreen(this)
96+
override fun getAssociatedFragment(): Fragment? = tabScreenDelegate.get()?.getFragmentForTabScreen(this)
9797

9898
private fun onTabFocusChangedFromJS() {
9999
tabScreenDelegate.get()?.onTabFocusChangedFromJS(this, isFocusedTab)

0 commit comments

Comments
 (0)