Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[LayoutAnimations] Fix exiting animations inside a Native Stack Navigator #3865

Merged
merged 16 commits into from
Jan 23, 2023

Conversation

jwajgelt
Copy link
Contributor

Summary

Currently, removing a screen with exiting layout animations running cause views to not be properly removed from the native hierarchy, as well as leaking memory in Layout Animations' AnimationManager.

This is caused by the ancestors of exiting views being removed from the native hierarchy by the React-Native-Screens implementation, while our implementation assumes they're still mounted in the native hierarchy.

Test plan

In the Example app, go to the "Nested NativeStacks with layout" screen and try toggling and navigating around while the exiting animation on the second screen of the nested stack is running.

@jwajgelt jwajgelt force-pushed the @jwajgelt/layout_anim_native_stack_fix branch from b594651 to 109ded6 Compare December 14, 2022 10:33
Comment on lines 289 to 294
if (indicesToRemove != null) {
for (int index : indicesToRemove) {
View child = viewGroupManager.getChildAt(viewGroup, index);
((ReaLayoutAnimator) mReaLayoutAnimator).cancelAnimationsInSubviews(child);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The fix for Android is to cancel all the animations in the transaction that removes the Screen.
This will cause all the exiting views to unregister their ancestors before being unmounted.

Comment on lines 284 to 313
NSNumber *parentTag = _exitingParentTags[child.reactTag];
[_exitingParentTags removeObjectForKey:child.reactTag];
while (parentTag != nil && ![parent isKindOfClass:[RCTRootView class]]) {
UIView *view = parent;
NSNumber *viewTag = parentTag;
parentTag = _exitingParentTags[viewTag];
UIView *viewByTag = [self viewForTag:viewTag];
parent = view.superview;

if (view == nil) {
if (viewByTag == nil) {
// the view was already removed from both native and RN hierarchies
// we can safely forget that it had any animated children
[_ancestorsToRemove removeObject:viewTag];
[_exitingSubviewsCountMap removeObjectForKey:viewTag];
[_exitingParentTags removeObjectForKey:viewTag];
continue;
}
// the child was dettached from view, but view is still
// in the native and RN hierarchy
view = viewByTag;
}

if (view.reactTag == nil) {
// we skip over views with no tag when registering parent tags,
// so we shouldn't go to the parent of viewTag yet
parentTag = viewTag;
continue;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On iOS, we can't simply cancel animations in the removed screen like on Android, since by the time manageChildren is called on the removed screen, it's already been removed from the native hierarchy.

Instead, when unregistering ancestors when the animations end, we use the tags of ancestors instead of view.superview refs to find the parents of the exiting views.

@kmagiera
Copy link
Member

I think this approach is ok. I'm only wondering if the changes that check for RNScreen are necessary for this to work. If I understand this correctly, it seem like these are separate things, and the changes that reference screens are there to prevent exiting views from animating when the whole screen is unmounted. If that's the case I think it'd be better to split these changes into separate PRs. Also blocking exiting animations in such a case may be in conflict with the work we do on SET (cc @piaskowyk )

@jwajgelt
Copy link
Contributor Author

jwajgelt commented Jan 4, 2023

I think this approach is ok. I'm only wondering if the changes that check for RNScreen are necessary for this to work. If I understand this correctly, it seem like these are separate things, and the changes that reference screens are there to prevent exiting views from animating when the whole screen is unmounted. If that's the case I think it'd be better to split these changes into separate PRs. Also blocking exiting animations in such a case may be in conflict with the work we do on SET (cc @piaskowyk )

On iOS, cancelling animations in subviews of a RNSScreenStackView fixes an issue when unmounting an entire stack with exiting animations inside the currently mounted screen keeping the entire stack alive for the duration of the animation (since in that case, Screens don't unmount the views themselves.)

@jwajgelt jwajgelt force-pushed the @jwajgelt/layout_anim_native_stack_fix branch from 416db71 to 3c4d0a6 Compare January 9, 2023 08:49
@jwajgelt jwajgelt marked this pull request as ready for review January 9, 2023 08:50
@jwajgelt jwajgelt changed the title [WIP][LayoutAnimations] Fix exiting animations inside a Native Stack Navigator [LayoutAnimations] Fix exiting animations inside a Native Stack Navigator Jan 11, 2023
@piaskowyk
Copy link
Member

Could you also resolve merge conflicts?

@piaskowyk
Copy link
Member

Could you also check how it works on native stack and js stack?

@jwajgelt jwajgelt force-pushed the @jwajgelt/layout_anim_native_stack_fix branch from cb6042a to e985442 Compare January 13, 2023 08:44
@jwajgelt
Copy link
Contributor Author

@piaskowyk

Could you also check how it works on native stack and js stack?

It does, though this shouldn't really change the behaviour with js stack.

@piaskowyk
Copy link
Member

This is the last iteration and then we could merge it

@piaskowyk piaskowyk merged commit e6e239a into main Jan 23, 2023
@piaskowyk piaskowyk deleted the @jwajgelt/layout_anim_native_stack_fix branch January 23, 2023 08:30
fluiddot pushed a commit to wordpress-mobile/react-native-reanimated that referenced this pull request Jun 5, 2023
…ator (software-mansion#3865)

<!-- Thanks for submitting a pull request! We appreciate you spending
the time to work on these changes. Please follow the template so that
the reviewers can easily understand what the code changes affect. -->

## Summary

Currently, removing a screen with `exiting` layout animations running
cause views to not be properly removed from the native hierarchy, as
well as leaking memory in Layout Animations' `AnimationManager`.

This is caused by the ancestors of exiting views being removed from the
native hierarchy by the React-Native-Screens implementation, while our
implementation assumes they're still mounted in the native hierarchy.

## Test plan

<!-- Provide a minimal but complete code snippet that can be used to
test out this change along with instructions how to run it and a
description of the expected behavior. -->

In the Example app, go to the "Nested NativeStacks with layout" screen
and try toggling and navigating around while the `exiting` animation on
the second screen of the nested stack is running.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants