Add workaround for android API 33 ANR when inverting ScrollView (#37913)

Summary:
As explained in this issue:

- https://github.com/facebook/react-native/issues/35350

starting from android API 33 there are severe performance issues when using `scaleY: -1` on a view, and its child view, which is what we are doing when inverting the `ScrollView` component (e.g. in `FlatList`).

This PR adds a workaround. The workaround is to also scale on the X-Axis which causes a different transform matrix to be created, that doesn't cause the ANR (see the issue for details).
However, when doing that the vertical scroll bar will be on the wrong side, thus we switch the position in the native code once we detect that the list is inverted.

The goal of this PR is that react-native users can just use `<FlatList inverted={true} />` without running into any ANRs or the need to apply manual hot fixes 😄

## Changelog:

<!-- Help reviewers and the release process by writing your own changelog entry.

Pick one each for the category and type tags:

[ANDROID] [FIXED] - ANR when having an inverted `FlatList` on android API 33+

For more details, see:
https://reactnative.dev/contributing/changelogs-in-pull-requests
-->

[ANDROID] [FIXED] - ANR when having an inverted `FlatList` on android API 33+

Pull Request resolved: https://github.com/facebook/react-native/pull/37913

Test Plan:
- The change is minimal, and only affects android.
- Run the RNTesterApp for android and confirm that in the flatlist example the inverted list is still working as expected.

Reviewed By: rozele

Differential Revision: D46871197

Pulled By: NickGerleman

fbshipit-source-id: 872a2ce5313f16998f0e4d2804d61e4d8dca7bfd
This commit is contained in:
Hanno J. Gödecke
2023-06-21 19:57:19 -07:00
committed by Facebook GitHub Bot
parent 74e6c95572
commit 90186cd9b7
4 changed files with 42 additions and 3 deletions
@@ -379,4 +379,22 @@ public class ReactScrollViewManager extends ViewGroupManager<ReactScrollView>
public void setScrollEventThrottle(ReactScrollView view, int scrollEventThrottle) {
view.setScrollEventThrottle(scrollEventThrottle);
}
@ReactProp(name = "inverted")
public void setInverted(ReactScrollView view, boolean inverted) {
// Usually when inverting the scroll view we are using scaleY: -1 on the list
// and on the parent container. HOWEVER, starting from android API 33 there is
// a bug that can cause an ANR due to that. Thus we are using different transform
// commands to circumvent the ANR. This however causes the vertical scrollbar to
// be on the wrong side. Thus we are moving it to the other side, when the list
// is inverted.
// See also:
// - https://github.com/facebook/react-native/issues/35350
// - https://issuetracker.google.com/issues/287304310
if (inverted) {
view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_LEFT);
} else {
view.setVerticalScrollbarPosition(View.SCROLLBAR_POSITION_DEFAULT);
}
}
}
@@ -319,7 +319,15 @@ ScrollViewProps::ScrollViewProps(
rawProps,
"scrollToOverflowEnabled",
sourceProps.scrollToOverflowEnabled,
{})) {}
{})),
inverted(
CoreFeatures::enablePropIteratorSetter ? sourceProps.inverted
: convertRawProp(
context,
rawProps,
"inverted",
sourceProps.inverted,
{})) {}
void ScrollViewProps::setProp(
const PropsParserContext &context,
@@ -368,6 +376,7 @@ void ScrollViewProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(snapToEnd);
RAW_SET_PROP_SWITCH_CASE_BASIC(contentInsetAdjustmentBehavior);
RAW_SET_PROP_SWITCH_CASE_BASIC(scrollToOverflowEnabled);
RAW_SET_PROP_SWITCH_CASE_BASIC(inverted);
}
}
@@ -492,7 +501,9 @@ SharedDebugStringConvertibleList ScrollViewProps::getDebugProps() const {
debugStringConvertibleItem(
"snapToStart", snapToStart, defaultScrollViewProps.snapToStart),
debugStringConvertibleItem(
"snapToEnd", snapToEnd, defaultScrollViewProps.snapToEnd)};
"snapToEnd", snapToEnd, defaultScrollViewProps.snapToEnd),
debugStringConvertibleItem(
"inverted", inverted, defaultScrollViewProps.inverted)};
}
#endif
@@ -68,6 +68,7 @@ class ScrollViewProps final : public ViewProps {
ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior{
ContentInsetAdjustmentBehavior::Never};
bool scrollToOverflowEnabled{false};
bool inverted{false};
#pragma mark - DebugStringConvertible
@@ -14,6 +14,7 @@ import type {
LayoutEvent,
ScrollEvent,
} from 'react-native/Libraries/Types/CoreEventTypes';
import Platform from 'react-native/Libraries/Utilities/Platform';
import type {ViewToken} from './ViewabilityHelper';
import type {
Item,
@@ -1969,7 +1970,15 @@ class VirtualizedList extends StateSafePureComponent<Props, State> {
const styles = StyleSheet.create({
verticallyInverted: {
transform: [{scaleY: -1}],
transform:
// Android 13 Bug Workaround:
// On Android, we need to invert both axes to mitigate a native bug
// that could lead to ANRs.
// Simply using scaleY: -1 leads to the application of scaleY and
// rotationX natively, resulting in the ANR.
// For more information, refer to the following Android tracking issue:
// https://issuetracker.google.com/issues/287304310
Platform.OS === 'android' ? [{scale: -1}] : [{scaleY: -1}],
},
horizontallyInverted: {
transform: [{scaleX: -1}],