Skip to content

feat(Android, Stack v5): allow for native navigation in nested stacks#3601

Merged
kkafar merged 5 commits intomainfrom
@kkafar/native-pop-in-nested-stack-2
Feb 2, 2026
Merged

feat(Android, Stack v5): allow for native navigation in nested stacks#3601
kkafar merged 5 commits intomainfrom
@kkafar/native-pop-in-nested-stack-2

Conversation

@kkafar
Copy link
Member

@kkafar kkafar commented Feb 1, 2026

Description

This commit refactors StackContainer code. Achieved functional effect
boils down to support for native-pop in nested stack, and in outer stack
after closing the nested one.

Closes https://github.com/software-mansion/react-native-screens-labs/issues/821

Changes

I've added abstraction over FragmentManager operations. This is done
mostly to structure and better manage container update code complexity.
The FragmentManager facing code is now moved into
FragmentOperationExecutor class.

Now FragmentManager reference is cleaned up in onDetachedFromWindow.

The StackContainer is now OnBackStackChangedListener. This is done
to better time & detect that a screen has been dismissed. This callback
is invoked mid-transaction execution by fragment manager, not after all
animations finish & the UI disappears. It seems like much more
appropriate place to keep the StackContainer possibly up-to-date.

Please note, that StackScreen still emits onDismiss event much
later, after the UI hides & fragment is destroyed. I've changed it a bit
here. Earlier it has been emitted in onDestroyView, now I emit it in
onDestroy. I've changed that to prevent incorrect emitting when
fragment moves to STARTED lifecycle state but is not detached from
fragment manager - such situation might happen when we use
FragmentTransaction.detach, which is planned.

This commit also adds basic primaryNavigationFragment management. This
is necessary for native navigation in nested stacks to work. If we do
not set it correctly, then childFragmentManager (used by nested stack)
won't handle backPressed at all. This responsibility will be delegated
to the supportFragmentManager and whole nested stack will be popped
immediately, instead of only single screen. Important thing to note here
is that we need to run additional fragment manager operations when we
detect that a fragment has been natively popped.
FragmentManager.primaryNavigationFragment state seems to not be
updated automatically once written to, hence unless we update it, we
will encounter a crash where FragmentManager attempts to delegate back
handling to already detached fragment.

Visual documentation

Push, Pop, Native pop work nicely, even when popping nested stack. The same action via JS popping should not work yet & will be handled separately.

Screen.Recording.2026-02-02.at.15.38.18.mov

There is no "before" video. This is initial implementation.

Test plan

TestStackNesting

Checklist

  • Included code example that can be used to test this change.
  • Updated / created local changelog entries in relevant test files.
  • For visual changes, included screenshots / GIFs / recordings documenting the change.
  • For API changes, updated relevant public types.
  • Ensured that CI passes

@kkafar kkafar marked this pull request as draft February 1, 2026 23:21
@kkafar kkafar force-pushed the @kkafar/native-pop-in-nested-stack branch from 60c9856 to 3807a8a Compare February 2, 2026 13:44
@kkafar kkafar force-pushed the @kkafar/native-pop-in-nested-stack-2 branch from c6d235c to ccae5b7 Compare February 2, 2026 14:04
Base automatically changed from @kkafar/native-pop-in-nested-stack to main February 2, 2026 14:33
This commit refactors *StackContainer* code. Achieved functional effect
boils down to support for native-pop in nested stack, and in outer stack
after closing the nested one.

I've added abstraction over *FragmentManager* operations. This is done
mostly to structure and better manage container update code complexity.
The *FragmentManager* facing code is now moved into
`FragmentOperationExecutor` class.

Now *FragmentManager* reference is cleaned up in `onDetachedFromWindow`.

The *StackContainer* is now `OnBackStackChangedListener`. This is done
to better time & detect that a screen has been dismissed. This callback
is invoked mid-transaction execution by fragment manager, not after all
animations finish & the UI disappears. It seems like much more
appropriate place to keep the *StackContainer* possibly up-to-date.

Please note, that *StackScreen* still emits `onDismiss` event much
later, after the UI hides & fragment is destroyed. I've changed it a bit
here. Earlier it has been emitted in `onDestroyView`, now I emit it in
`onDestroy`. I've changed that to prevent incorrect emitting when
fragment moves to *STARTED* lifecycle state but is not detached from
fragment manager - such situation might happen when we use
`FragmentTransaction.detach`, which is planned.

This commit also adds basic *primaryNavigationFragment* management. This
is necessary for native navigation in nested stacks to work. If we do
not set it correctly, then *childFragmentManager* (used by nested stack)
won't handle `backPressed` at all. This responsibility will be delegated
to the *supportFragmentManager* and whole nested stack will be popped
immediately, instead of only single screen. Important thing to note here
is that we need to run additional fragment manager operations when we
detect that a fragment has been natively popped.
`FragmentManager.primaryNavigationFragment` state seems to not be
updated automatically once written to, hence unless we update it, we
will encounter a crash where *FragmentManager* attempts to delegate back
handling to already detached fragment.
@kkafar kkafar force-pushed the @kkafar/native-pop-in-nested-stack-2 branch from ccae5b7 to 5500d7e Compare February 2, 2026 14:34
@kkafar
Copy link
Member Author

kkafar commented Feb 2, 2026

Note

Do not test this via Example. New stack does not work nicely with v4 implementation, and likely we won't put time into making it work.

@kkafar
Copy link
Member Author

kkafar commented Feb 2, 2026

This PR uses following patch:

diff --git a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt
index 145fb50fc..f2863cefb 100644
--- a/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt
+++ b/FabricExample/android/app/src/main/java/com/fabricexample/MainActivity.kt
@@ -25,6 +25,15 @@ class MainActivity : ReactActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
     supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
     super.onCreate(savedInstanceState)
+
+    try {
+      val field = ReactActivity::class.java.getDeclaredField("mBackPressedCallback")
+      field.isAccessible = true
+      val callback = field.get(this) as androidx.activity.OnBackPressedCallback
+      callback.isEnabled = false // <--- KILL SWITCH
+    } catch (e: Exception) {
+      e.printStackTrace()
+    }
   }
 
   override fun onAttachedToWindow() {

This makes sure the predictive back gesture to launcher screen works nicely.

Copy link
Member Author

@kkafar kkafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left some debug logs in the implementation for now intentionally. The implementation will take place for some more time & I don't want to set up all logs for debugging on each PR. This is useful right now & we'll clean it up only after we release alpha / beta version of 5.0.

I've created ticket for that: https://github.com/software-mansion/react-native-screens-labs/issues/925

@kkafar kkafar marked this pull request as ready for review February 2, 2026 14:49
@kkafar kkafar merged commit 82ce103 into main Feb 2, 2026
4 of 5 checks passed
@kkafar kkafar deleted the @kkafar/native-pop-in-nested-stack-2 branch February 2, 2026 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants