fix(Android, Stack): dispatch lifecycle events when parent is not a ScreenFragment#3854
Conversation
There was a problem hiding this comment.
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 aScreenFragment. - Add explanatory comments documenting the brownfield/
ReactFragmentscenario 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)) { |
There was a problem hiding this comment.
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).
| if (parent == null || parent !is ScreenFragment || (parent is ScreenFragment && !parent.isTransitioning)) { | |
| if (parent !is ScreenFragment || !parent.isTransitioning) { |
There was a problem hiding this comment.
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
Description
When React Native is loaded inside a
Fragment(not directly in anActivity), for example in a brownfield setup usingReactFragment, lifecycle events are never dispatched for root screen fragments. This causestransitionStart/transitionEndevents in react-navigation to never fire.The root cause is in
ScreenFragment.dispatchViewAnimationEvent(). The condition:does not account for the case where
parentFragmentis a regularFragment. When RN runs inside aReactFragment,findFragmentManagerForReactRootViewreturns that fragment'schildFragmentManager. Root screen fragments then haveparentFragmentpointing to the host fragment. Since it's notnulland not aScreenFragment, the condition evaluated tofalseand all lifecycle events were silently dismissed.Changes
Added
parent !is ScreenFragmentto the condition inScreenFragment.dispatchViewAnimationEvent(), so that lifecycle events are correctly dispatched when the parent fragment is not aScreenFragment(e.g.ReactFragmentin brownfield apps). Such parents don't participate in screen transitions, so they should be treated the same asparent == 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