react-native-gesture-handler: ScrollView breaks RefreshControl on Android
When using ScrollView from rngh RefreshControl no longer works. This happens because on Android RefreshControl works by wrapping the ScrollView with a SwipeRefreshLayout component. This component should interact with the rngh gesture system since it needs to be able to interrupt and recognize simultaneously the ScrollView it wraps.
This can be kind of accomplished by wrapping RefreshControl with createNativeWrapper
on Android. This is the first part of the hack patch I have to fix this:
GestureComponents.js
});
},
+ get RefreshControl() {
+ if (Platform.OS === 'android') {
+ return memoizeWrap(ReactNative.RefreshControl, {
+ disallowInterruption: true,
+ shouldCancelWhenOutside: false,
+ });
+ } else {
+ return ReactNative.RefreshControl;
+ }
+ },
get Switch() {
return memoizeWrap(ReactNative.Switch, {
The problem then is that ScrollView sets disallowInterruption
to true, which means it cannot get interrupted or recognize with another gesture handler. Setting disallowInterruption
to false makes RefreshControl work but causes other issues like nested ScrollView will both scroll at the same time.
I tried playing with adding simultaneousHandlers
or waitFor
to the RefreshControl associated with the ScrollView but wasn’t able to get it working. Seems like disallowInterruption
takes priority over that.
So at this point I was mostly looking for a hack to get it working so I came up with:
NativeViewGestureHandler.java
import android.view.ViewGroup;
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
+
public class NativeViewGestureHandler extends GestureHandler<NativeViewGestureHandler> {
private boolean mShouldActivateOnStart;
@@ -48,7 +50,7 @@ public class NativeViewGestureHandler extends GestureHandler<NativeViewGestureHa
}
}
- boolean canBeInterrupted = !mDisallowInterruption;
+ boolean canBeInterrupted = !shouldDisallowInterruptionBy(handler);
int state = getState();
int otherState = handler.getState();
@@ -62,9 +64,19 @@ public class NativeViewGestureHandler extends GestureHandler<NativeViewGestureHa
return state == STATE_ACTIVE && canBeInterrupted;
}
+ private boolean shouldDisallowInterruptionBy(GestureHandler handler) {
+ if (handler.getView() instanceof SwipeRefreshLayout) {
+ return false;
+ }
+ return mDisallowInterruption;
+ }
+
@Override
public boolean shouldBeCancelledBy(GestureHandler handler) {
- return !mDisallowInterruption;
+ if (handler.getView() instanceof SwipeRefreshLayout) {
+ return true;
+ }
+ return !shouldDisallowInterruptionBy(handler);
}
@Override
Basically it just special cases when the other handler is SwipeRefreshLayout so that even if mDisallowInterruption is true it will treat it like it was false.
Not sure how this could be properly fix, will keep using this hack for now.
About this issue
- Original URL
- State: closed
- Created 4 years ago
- Reactions: 10
- Comments: 33 (7 by maintainers)
I had the same issue with the scrollview from react-native-gesture-handler tried so many methods finally i just switched to scrollview from react-native to solve this issue
I debugged this for a bit and looks like refresh control is getting interrupted by scroll view (that has
disallowInterruption: true
). I’m considering some solutions to this problem but I want to avoid adding a special case for ScrollView.As a workaround for now, you can wrap Refresh Control in NativeView Gesture handler and use
waitFor
with it:Full example
I had this issue as well, switched the import of ScrollView from ‘react-native-gesture-handler’ to ‘react-native’ , to get it to work for now
I ran into this issue today as well. A RNGH scrollview with content that is large enough to scroll. Adding a RefreshControl. And it doesn’t work. If the content is small enough that no scrolling is needed it does work. Im 100% sure this worked a few months ago when i build these screens.
Any idea on how to fix this properly? And what are the downsides of using the RN scrollview? this was never 100% clear to me (why the RNGH scrollview is better).
edit: @jakub-gonet can you comment on this?
In case this can help anybody using a custom wrapper, this fixed the issue for me :
I am experiencing the exact same issue as above. Having a wrapper that encapsulates the states and custom styles is preventing the rendering of children, only in Android. Trying to switch ScrollView / RefreshControl for the react-native ones did not help I am using RN
0.70.3
and react-native-gesture-handler2.8.0
@janicduplessis, I tried to reproduce it and seems like it’s not working. After replacing
ScrollView
with RN one it still doesn’t work. It’s a problem with RNGH though, I’ll try to deep deeper into that.In my case the content is not being rendered. I am using a VirtualList and ScrollView in some places to implement PullToRefresh functionality. I am using memoized JSX and passing it to the
refreshControl
prop of ScrollView/VirtualList.However when I am directly using the component like this, its working fine for me in Android Platform.
react-native-version:0.68.2
This issue also exists when importing FlatList from react-native-gesture-handler and wrapping it in react-native-reanimated Animated.createAnimatedComponent. Wrapping RefreshControl in createNativeWrapper does not resolve the issue. The pull to refresh event fails to fire on Android as demonstrated in this Snack.
A work around is to import FlatList directly from react-native, not wrap the component in reanimated and set the Animated value manually on the FlatList onScroll event, but this is far less performant than using Animated.Event and ends up blocking the UI thread on slow devices.
@vivekjm Error is gone after following what you wrote, but content inside
ScrollView
is not shown as @pke mentioned.EDIT: I noticed that when I import a component that returns
RefreshControl
, the content ofScrollView
is not shown, but when I useRefreshControl
directly withScrollView
it works as expected (Android physical device).You can use
renderScrollComponent
prop to passScrollView
with those props set. Or you can patch-package RNGH to apply this patch from master and use waitFor with ref directly on FlatList.@FRizzonelli no i have not 😦
Same here. I tried multiple times, and few of the times it worked. Is it the same with you that randomly it works?
Hmm strange, I can repro using the same code in my app by replacing everything in App.js, but can’t repro in snack. Issue might be related to the react-native version I’m using in my app, will investigate more.
@jakub-gonet do you maybe have time to prioritize this one?
@janicduplessis
I got it working by putting a ScrollView inside a ScrollView so the code looks like this:
What side effects should I expect ?