Skip to content

Commit d62b0a3

Browse files
committed
feat(Android): add basic support for nesting v4 stack in new bottom-tabs (#158)
## Description Closes #159 This PR adds support for nesting v4 stack in new bottom tabs component on Android. Currently, an attempt to do so would crash the application with known error "FragmentManager is already executing transactions". This is, because both `TabsHost` & `ScreenStack` would use the same `FragmentManager` (application `supportFragmentManager`). This PR adds support for **nesting ScreenStack inside TabsHost** (note that **only for this configuration**), by adjusting fragment manager finding code in v4 stack implementation and adding necessary logic in `TabScreen`. ## Changes Please see particular commits for precise description. * [Add support for FragmentProviding in v4 code](software-mansion/react-native-screens-labs@d0ca74f) This allows v4 stack to discover that it is nested inside gamma container. The interop is not full, because `ScreenContainer` expects the parent that provides fragment manager to also provide `FragmentWrapper` and this is not supported for gamma containers. This incompatibility prevents communication between v4 & gamma containers. I do not recall exactly what won't work, but something surely won't. * [Add method to TabScreenDelegate providing fragment acccess for `TabScreen`](software-mansion/react-native-screens-labs@319fe7e) I don't really like this method being added to the main delegate, however I want to keep `TabScreen` unaware of its hosting fragment (as long as possible). Simultaneously, when looking for fragment manager from nested stack, the `TabScreen` is the place we need to stop the search at, otherwise `TabsHost` wouldn't know under which tab the nested stack is hosted. We could do some reasonable assumptions, e.g. focused tab, however I'm not sure this would work (separate discussion). Therefore `TabScreen` needs to be aware of the `fragment` - and we end up with contradiction. This led me to putting a provider method on the `TabScreenDelegate`. Otherwise I would have to create another delegate interface & set / remove another delegate. I guess it would be cleaner, but seemed like an overkill for now. We might do that later, however. ## Test code and steps to reproduce I don't want to ruin the current example. We need to extract the `BottomTabsContainer` & `StackContainer` components, so that they can be reused across different tests. ## Checklist - [ ] Ensured that CI passes
1 parent 8ca3d94 commit d62b0a3

File tree

4 files changed

+24
-2
lines changed

4 files changed

+24
-2
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import com.facebook.react.uimanager.ThemedReactContext
1818
import com.facebook.react.uimanager.UIManagerHelper
1919
import com.swmansion.rnscreens.Screen.ActivityState
2020
import com.swmansion.rnscreens.events.ScreenDismissedEvent
21+
import com.swmansion.rnscreens.gamma.common.FragmentProviding
2122

2223
open class ScreenContainer(
2324
context: Context?,
@@ -175,7 +176,7 @@ open class ScreenContainer(
175176
private fun setupFragmentManager() {
176177
var parent: ViewParent = this
177178
// We traverse view hierarchy up until we find screen parent or a root view
178-
while (!(parent is ReactRootView || parent is Screen) &&
179+
while (!(parent is ReactRootView || parent is Screen || parent is FragmentProviding) &&
179180
parent.parent != null
180181
) {
181182
parent = parent.parent
@@ -190,6 +191,13 @@ open class ScreenContainer(
190191
setFragmentManager(fragmentWrapper.fragment.childFragmentManager)
191192
},
192193
) { "Parent Screen does not have its Fragment attached" }
194+
} else if (parent is FragmentProviding) {
195+
// TODO: We're missing parent-child relationship here between old container & new one
196+
val fragmentManager =
197+
checkNotNull(
198+
parent.getFragment(),
199+
) { "[RNScreens] Parent $parent returned nullish fragment" }.childFragmentManager
200+
setFragmentManager(fragmentManager)
193201
} else {
194202
// we expect top level view to be of type ReactRootView, this isn't really necessary but in
195203
// order to find root view we test if parent is null. This could potentially happen also when

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package com.swmansion.rnscreens.gamma.tabs
22

33
import android.util.Log
44
import android.view.ViewGroup
5+
import androidx.fragment.app.Fragment
56
import com.facebook.react.uimanager.ThemedReactContext
7+
import com.swmansion.rnscreens.gamma.common.FragmentProviding
68
import java.lang.ref.WeakReference
79
import kotlin.properties.Delegates
810

@@ -11,7 +13,8 @@ import kotlin.properties.Delegates
1113
*/
1214
class TabScreen(
1315
val reactContext: ThemedReactContext,
14-
) : ViewGroup(reactContext) {
16+
) : ViewGroup(reactContext),
17+
FragmentProviding {
1518
override fun onLayout(
1619
changed: Boolean,
1720
l: Int,
@@ -57,6 +60,8 @@ class TabScreen(
5760
tabScreenDelegate = WeakReference(delegate)
5861
}
5962

63+
override fun getFragment(): Fragment? = tabScreenDelegate.get()?.getFragmentForTabScreen(this)
64+
6065
private fun onTabFocusChangedFromJS() {
6166
tabScreenDelegate.get()?.onTabFocusChangedFromJS(this, isFocusedTab)
6267
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
package com.swmansion.rnscreens.gamma.tabs
22

3+
import androidx.fragment.app.Fragment
4+
35
internal interface TabScreenDelegate {
46
fun onTabFocusChangedFromJS(
57
tabScreen: TabScreen,
68
isFocused: Boolean,
79
)
810

911
fun onMenuItemAttributesChange(tabScreen: TabScreen)
12+
13+
/**
14+
* This returns fragment **if one is associated with given tab screen**.
15+
*/
16+
fun getFragmentForTabScreen(tabScreen: TabScreen): Fragment?
1017
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,8 @@ class TabsHost(
216216
}
217217
}
218218

219+
override fun getFragmentForTabScreen(tabScreen: TabScreen): TabScreenFragment? = tabScreenFragments.find { it.tabScreen === tabScreen }
220+
219221
private fun updateBottomNavigationViewAppearance() {
220222
Log.w(TAG, "updateBottomNavigationViewAppearance")
221223
bottomNavigationView.isVisible = true

0 commit comments

Comments
 (0)