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

Fix view indices with Android LayoutAnimation (attempt 2) #19775

Closed
wants to merge 2 commits into from
Closed

Fix view indices with Android LayoutAnimation (attempt 2) #19775

wants to merge 2 commits into from

Conversation

lnikkila
Copy link
Contributor

/cc @janicduplessis @mdvacca

This addresses the same issue as #18830 which was reverted since it didn’t handle removeClippedSubviews properly.

When subview clipping is on, ReactViewGroup keeps track of its own children directly and accounts for the offset introduced by clipped views when calling ViewGroup methods that modify children.

Instead of accounting for just clipped children (views with no parent), it should account for any children that aren’t in the ViewGroup which also includes children that are being transitioned. If you look at the ViewGroup source code, it explicitly retains the view parent until the transition finishes which caused the getParent() checks to pass, even though those views should be ignored. I added a new isChildInViewGroup method that handles both clipped and transitioning views to fix this.

Test Plan

I reproduced the earlier crash by enabling clipping in this test app and adding a “clear views” button that resets the state to an empty items array with an animation.

Related PRs

Release Notes

[ANDROID] [BUGFIX] [LayoutAnimation] - Removal LayoutAnimations no longer remove adjacent views as well in certain cases.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Jun 17, 2018
Copy link
Contributor

@janicduplessis janicduplessis left a comment

Choose a reason for hiding this comment

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

Nice work, sorry about the delay reviewing this!

@janicduplessis
Copy link
Contributor

@mdvacca Could you have a look to make sure this is fine?

@facebook-github-bot
Copy link
Contributor

@lnikkila I tried to find reviewers for this pull request and wanted to ping them to take another look. However, based on the blame information for the files in this pull request I couldn't find any reviewers. This sometimes happens when the files in the pull request are new or don't exist on master anymore. Is this pull request still relevant? If yes could you please rebase? In case you know who has context on this code feel free to mention them in a comment (one person is fine). Thanks for reading and hope you will continue contributing to the project.

@nickarora
Copy link

@janicduplessis can you merge this in?

@janicduplessis
Copy link
Contributor

@facebook-github-bot shipit

@facebook-github-bot facebook-github-bot added the Import Started This pull request has been imported. This does not imply the PR has been approved. label Jul 31, 2018
Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

janicduplessis is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@facebook-github-bot
Copy link
Contributor

I tried to merge this pull request into the Facebook internal repo but some checks failed. To unblock yourself please check the following: Does this pull request pass all open source tests on GitHub? If not please fix those. Does the code still apply cleanly on top of GitHub master? If not can please rebase. In all other cases this means some internal test failed, for example a part of a fb app won't work with this pull request. I've added the Import Failed label to this pull request so it is easy for someone at fb to find the pull request and check what failed. If you don't see anyone comment in a few days feel free to comment mentioning one of the core contributors to the project so they get a notification.

@facebook-github-bot facebook-github-bot added Import Failed and removed Import Started This pull request has been imported. This does not imply the PR has been approved. labels Aug 1, 2018
Fixes issue #11828 that causes layout animations for removed views to
remove some adjacent views as well. This happens because the animated
views are still present in the ViewGroup, which throws off subsequent
operations that rely on view indices having updated.

This issue was addressed in #11962, which was closed in favour of a more
reliable solution that addresses the issue globally since it’s difficult
to account for animated views everywhere. @janicduplessis recommended[0]
handling the issue through ViewManager.

Since API 11, Android provides `ViewGroup#startViewTransition(View)`
that can be used to keep child views visible even if they have been
removed from the group. ViewGroup keeps an array of these views, which
is only used for drawing. Methods such as `ViewGroup#getChildCount()`
and `ViewGroup#getChildAt(int)` will ignore them.

I believe relying on these framework methods within ViewManager is the
most reliable way to solve this issue because it also works if callers
ignore ViewManager and reach into the native view indices and counts
directly.

[0]: #11962 (review)
@lnikkila
Copy link
Contributor Author

lnikkila commented Aug 1, 2018

@janicduplessis Rebased, if that helps. I noticed there was a three-way merge required for ReactViewGroup, so I went through the changes in master since and I don’t think there are any that would affect this.

@janicduplessis
Copy link
Contributor

👍 Let's try landing it again

@facebook-github-bot shipit

@facebook-github-bot facebook-github-bot added the Import Started This pull request has been imported. This does not imply the PR has been approved. label Aug 2, 2018
@janicduplessis
Copy link
Contributor

@facebook-github-bot shipit

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

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

janicduplessis is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@react-native-bot
Copy link
Collaborator

This pull request was closed by @lnikkila in 1f88a71.

Once this commit is added to a release, you will see the corresponding version tag below the description at 1f88a71. If the commit has a single master tag, it is not yet part of a release.

@facebook facebook locked as resolved and limited conversation to collaborators Aug 2, 2018
@react-native-bot react-native-bot added the Merged This PR has been merged. label Aug 2, 2018
@hramos
Copy link
Contributor

hramos commented Aug 27, 2018

This change is getting reverted on master, as we found it to be the cause of the following exception when used in our own products:

android_crash:java.lang.IndexOutOfBoundsException:com.facebook.react.views.view.ReactViewGroup.updateSubviewClipStatus
stack_trace:	java.lang.IndexOutOfBoundsException: Index: 3, Size: 2
	at java.util.ArrayList.get(ArrayList.java:437)
	at android.view.ViewGroup.clearDisappearingChildren(ViewGroup.java:7029)
	at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:3880)
	at android.view.ViewGroup.removeViewsInternal(ViewGroup.java:5552)
	at android.view.ViewGroup.removeViewsInLayout(ViewGroup.java:5366)
	at com.facebook.react.views.view.ReactViewGroup.updateSubviewClipStatus(ReactViewGroup.java:366)
	at com.facebook.react.views.view.ReactViewGroup.updateClippingToRect(ReactViewGroup.java:342)
	at com.facebook.react.views.view.ReactViewGroup.updateClippingRect(ReactViewGroup.java:335)
	at com.facebook.react.views.view.ReactViewGroup.onSizeChanged(ReactViewGroup.java:428)
	at android.view.View.sizeChange(View.java:20988)
	at android.view.View.setFrame(View.java:20930)
	at android.view.View.layout(View.java:20833)
	at android.view.ViewGroup.layout(ViewGroup.java:6401)
	at com.facebook.react.uimanager.layoutanimation.PositionAndSizeAnimation.applyTransformation(PositionAndSizeAnimation.java:35)
	at android.view.animation.Animation.getTransformation(Animation.java:879)
	at android.view.animation.Animation.getTransformation(Animation.java:953)
	at android.view.View.applyLegacyAnimation(View.java:19897)
	at android.view.View.draw(View.java:20013)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4421)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4207)
	at android.view.View.draw(View.java:20355)
	at android.widget.ScrollView.draw(ScrollView.java:2731)
	at com.facebook.react.views.scroll.ReactScrollView.draw(ReactScrollView.java:357)
	at android.view.View.updateDisplayListIfDirty(View.java:19297)
	at android.view.View.draw(View.java:20075)
	at android.view.ViewGroup.drawChild(ViewGroup.java:4421)
	at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4207)
	at android.view.View.draw(View.java:20355)
	at android.view.View.updateDisplayListIfDirty(View.java:19297)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4405)
	at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4385)
	at android.view.View.updateDisplayListIfDirty(View.java:19256)
	at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:686)
	at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:692)
	at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:800)
	at android.view.ViewRootImpl.draw(ViewRootImpl.java:3488)
	at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3275)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2810)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1779)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7800)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
	at android.view.Choreographer.doCallbacks(Choreographer.java:723)
	at android.view.Choreographer.doFrame(Choreographer.java:658)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
	at android.os.Handler.handleCallback(Handler.java:789)
	at android.os.Handler.dispatchMessage(Handler.java:98)
	at android.os.Looper.loop(Looper.java:164)
	at android.app.ActivityThread.main(ActivityThread.java:6938)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:327)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

@hramos hramos removed the Import Started This pull request has been imported. This does not imply the PR has been approved. label Feb 6, 2019
@hramos hramos removed Import Started This pull request has been imported. This does not imply the PR has been approved. Import Failed labels Feb 6, 2019
daniel-nagy pushed a commit to Boulevard/react-native that referenced this pull request Jul 22, 2021
…9775)

Summary:
/cc janicduplessis mdvacca

This addresses the same issue as facebook#18830 which was reverted since it didn’t handle `removeClippedSubviews` properly.

When subview clipping is on, ReactViewGroup keeps track of its own children directly and accounts for the offset introduced by clipped views when calling ViewGroup methods that modify children.

Instead of accounting for just clipped children (views with no parent), it should account for any children that aren’t in the ViewGroup which also includes children that are being transitioned. If you look at the ViewGroup source code, [it explicitly retains the view parent until the transition finishes](https://github.com/aosp-mirror/platform_frameworks_base/blob/oreo-release/core/java/android/view/ViewGroup.java#L5034-L5036) which caused the `getParent()` checks to pass, even though those views should be ignored. I added a new `isChildInViewGroup` method that handles both clipped and transitioning views to fix this.

I reproduced the [earlier crash](facebook#18830 (comment)) by enabling clipping in [this test app](facebook#18830 (review)) and adding a “clear views” button that resets the state to an empty items array with an animation.

- facebook#18830

[ANDROID] [BUGFIX] [LayoutAnimation] - Removal LayoutAnimations no longer remove adjacent views as well in certain cases.
Pull Request resolved: facebook#19775

Differential Revision: D9105838

Pulled By: hramos

fbshipit-source-id: 5ccb0957d1f46c36add960c0e4ef2a545cb03cbe
soto-blvd pushed a commit to soto-blvd/react-native that referenced this pull request Feb 15, 2022
…9775)

Summary:
/cc janicduplessis mdvacca

This addresses the same issue as facebook#18830 which was reverted since it didn’t handle `removeClippedSubviews` properly.

When subview clipping is on, ReactViewGroup keeps track of its own children directly and accounts for the offset introduced by clipped views when calling ViewGroup methods that modify children.

Instead of accounting for just clipped children (views with no parent), it should account for any children that aren’t in the ViewGroup which also includes children that are being transitioned. If you look at the ViewGroup source code, [it explicitly retains the view parent until the transition finishes](https://github.com/aosp-mirror/platform_frameworks_base/blob/oreo-release/core/java/android/view/ViewGroup.java#L5034-L5036) which caused the `getParent()` checks to pass, even though those views should be ignored. I added a new `isChildInViewGroup` method that handles both clipped and transitioning views to fix this.

I reproduced the [earlier crash](facebook#18830 (comment)) by enabling clipping in [this test app](facebook#18830 (review)) and adding a “clear views” button that resets the state to an empty items array with an animation.

- facebook#18830

[ANDROID] [BUGFIX] [LayoutAnimation] - Removal LayoutAnimations no longer remove adjacent views as well in certain cases.
Pull Request resolved: facebook#19775

Differential Revision: D9105838

Pulled By: hramos

fbshipit-source-id: 5ccb0957d1f46c36add960c0e4ef2a545cb03cbe
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Bug CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Merged This PR has been merged. Platform: Android Android applications.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants