Skip to content

Commit

Permalink
Add workaround for android API 33 ANR when inverting ScrollView (#37913)
Browse files Browse the repository at this point in the history
Summary:
As explained in this issue:

- #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: #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
  • Loading branch information
hannojg authored and facebook-github-bot committed Jun 22, 2023
1 parent 74e6c95 commit 90186cd
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,22 @@ public void setPointerEvents(ReactScrollView view, @Nullable String pointerEvent
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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class ScrollViewProps final : public ViewProps {
ContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior{
ContentInsetAdjustmentBehavior::Never};
bool scrollToOverflowEnabled{false};
bool inverted{false};

#pragma mark - DebugStringConvertible

Expand Down
11 changes: 10 additions & 1 deletion packages/virtualized-lists/Lists/VirtualizedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}],
Expand Down

0 comments on commit 90186cd

Please sign in to comment.