Skip to content

fix(Android, Stack): dispatch lifecycle events when parent is not a ScreenFragment#3854

Merged
kkafar merged 2 commits intosoftware-mansion:mainfrom
collectioneur:fix/android-dispatch-lifecycle-events-non-screen-fragment-parent
Apr 15, 2026
Merged

fix(Android, Stack): dispatch lifecycle events when parent is not a ScreenFragment#3854
kkafar merged 2 commits intosoftware-mansion:mainfrom
collectioneur:fix/android-dispatch-lifecycle-events-non-screen-fragment-parent

Conversation

@collectioneur
Copy link
Copy Markdown
Contributor

@collectioneur collectioneur commented Apr 8, 2026

Description

When React Native is loaded inside a Fragment (not directly in an Activity), for example in a brownfield setup using ReactFragment, lifecycle events are never dispatched for root screen fragments. This causes transitionStart/transitionEnd events in react-navigation to never fire.

The root cause is in ScreenFragment.dispatchViewAnimationEvent(). The condition:

if (parent == null || (parent is ScreenFragment && !parent.isTransitioning))

does not account for the case where parentFragment is a regular Fragment. When RN runs inside a ReactFragment, findFragmentManagerForReactRootView returns that fragment's childFragmentManager. Root screen fragments then have parentFragment pointing to the host fragment. Since it's not null and not a ScreenFragment, the condition evaluated to false and all lifecycle events were silently dismissed.

Changes

Added parent !is ScreenFragment to the condition in ScreenFragment.dispatchViewAnimationEvent(), so that lifecycle events are correctly dispatched when the parent fragment is not a ScreenFragment (e.g. ReactFragment in brownfield apps). Such parents don't participate in screen transitions, so they should be treated the same as parent == null.

Before & after - visual documentation

Before

before.mov

After

after.mov

Test plan

Reproducing this in FabricExample without adding a brownfield component is not possible. However, I tested all events in the example app after applying the change and everything works the same as before.

Checklist

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

@collectioneur collectioneur changed the title fix(Android, Stack): dispatch lifecycle events when parent is not a S… fix(Android, Stack): dispatch lifecycle events when parent is not a ScreenFragment Apr 8, 2026
@collectioneur collectioneur marked this pull request as ready for review April 8, 2026 12:35
@kkafar kkafar requested review from Copilot and kkafar April 8, 2026 12:42
@kkafar kkafar self-assigned this Apr 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes missing screen lifecycle dispatch for root ScreenFragments when React Native is hosted inside a non-ScreenFragment parent (e.g., ReactFragment in brownfield apps), restoring transitionStart/transitionEnd behavior in react-navigation.

Changes:

  • Update ScreenFragment.dispatchViewAnimationEvent() to dispatch lifecycle events when the parent fragment is not a ScreenFragment.
  • Add explanatory comments documenting the brownfield/ReactFragment scenario and rationale.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// in a brownfield setup), we always dispatch. Such parents don't participate in screen transitions.
val parent = parentFragment
if (parent == null || (parent is ScreenFragment && !parent.isTransitioning)) {
if (parent == null || parent !is ScreenFragment || (parent is ScreenFragment && !parent.isTransitioning)) {
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

The if condition is logically correct but redundant/verbose: parent == null is already covered by parent !is ScreenFragment, and the final (parent is ScreenFragment && ...) re-check can be avoided. Consider simplifying to if (parent !is ScreenFragment || !parent.isTransitioning) (which is also shorter and more readable).

Suggested change
if (parent == null || parent !is ScreenFragment || (parent is ScreenFragment && !parent.isTransitioning)) {
if (parent !is ScreenFragment || !parent.isTransitioning) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My current version is a bit easier to read:
Case 1: If there's no parent Fragment, we simply dispatch the events.
Case 2: If a parent Fragment exists but it isn't a ScreenFragment, it won't dispatch lifecycle events on its own (since only ScreenFragments do that), so we can safely dispatch the events ourselves.
Case 3: If the parent is a ScreenFragment but isn't currently transitioning, we know there won't be any duplicates, so we dispatch the event here too.
In all other cases, we just don't.

It's still clear and not redundant
Copy link
Copy Markdown
Member

@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.

Thanks!

@kkafar kkafar merged commit b9fafba into software-mansion:main Apr 15, 2026
5 checks passed
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.

3 participants