question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

ScrollView breaks RefreshControl on Android

See original GitHub issue

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.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:9
  • Comments:28 (7 by maintainers)

github_iconTop GitHub Comments

62reactions
vivekjmcommented, Nov 10, 2020

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

23reactions
jakub-gonetcommented, Aug 13, 2021

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:

export const RefreshControl = createNativeWrapper(RNRefreshControl, {
  disallowInterruption: true,
  shouldCancelWhenOutside: false,
});

const refreshRef = useRef(null);

<ScrollView	waitFor={refreshRef} refreshControl={<RefreshControl ref={refreshRef} />}>
	{/* ... */}
</ScrollView>
Full example
import React, { useCallback, useRef } from 'react';
import { View, Text, RefreshControl as RNRefreshControl } from 'react-native';
import { createNativeWrapper, ScrollView } from 'react-native-gesture-handler';

const RefreshControl = createNativeWrapper(RNRefreshControl, {
  disallowInterruption: true,
  shouldCancelWhenOutside: false,
});

const wait = (timeout: number) => {
  return new Promise<number>((resolve) => {
    setTimeout(resolve, timeout);
  });
};

export default function App() {
  const refreshRef = useRef(null);
  const [refreshing, setRefreshing] = React.useState(false);
  console.log(refreshing);
  const onRefresh = useCallback(() => {
    setRefreshing(true);

    void wait(1000).then(() => setRefreshing(false));
  }, []);

  return (
    <ScrollView
      waitFor={refreshRef}
      style={{ flex: 1 }}
      refreshControl={
        <RefreshControl
          ref={refreshRef}
          refreshing={refreshing}
          onRefresh={onRefresh}
        />
      }>
      <View
        style={{
          height: 2000,
          paddingTop: 64,
          backgroundColor: 'pink',
          alignItems: 'center',
        }}>
        <Text>Pull down to see RefreshControl indicator</Text>
      </View>
    </ScrollView>
  );
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

React native: Scroll view refresh control pull to refresh not ...
I have added pull to refresh inside web view using scroll view. It is working but the condition is that pull ...
Read more >
React Native Swipe Down to Refresh List View Using Refresh ...
Here is an example of React Native Swipe Down to Refresh List View Using Refresh Control. It was first introduced in Android Material...
Read more >
RefreshControl - Pull to Refresh in React Native Apps - Enappd
Pull to Refresh Example in Android ... This is my break-down of the blog ... RefreshControl is used inside a ScrollView or ListView...
Read more >
How to Use Pull to Refresh in React Native - RefreshControl
You can use RefreshControl inside a ScrollView/ListView/Flatlist to add pull to refresh functionality. import { RefreshControl } from 'react- ...
Read more >
React Native v0.65.x released
Modified NativeEventEmitter to also use the passed native module to report subscriptions on Android (f5502fbda9 by @rubennorte) · RefreshControl.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found