Skip to content

[Android] Stop nested scroll when a native handler's gesture ends - #4492

Open
m-bert wants to merge 1 commit into
mainfrom
@mbert/fix-rn-refresh-control-android
Open

[Android] Stop nested scroll when a native handler's gesture ends#4492
m-bert wants to merge 1 commit into
mainfrom
@mbert/fix-rn-refresh-control-android

Conversation

@m-bert

@m-bert m-bert commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Description

ScrollView and FlatList from Gesture Handler never fire onRefresh when given React Native's RefreshControl on Android. The spinner follows the pull but stays parked on release, and the refresh only triggers on the next touch anywhere on the screen.

With a refreshControl RN wraps the scroll view in a SwipeRefreshLayout and enables nested scrolling on it. The pull reaches the layout through the nested-scroll API, and the layout finishes it (fires refresh or snaps back) only in onStopNestedScroll. Android calls stopNestedScroll() from View.dispatchTouchEvent at the end of a gesture, but once NativeViewGestureHandler activates it delivers touches straight to the view's onTouchEvent, so that cleanup never runs. The nested scroll stays open and the layout is released one touch late, by the CANCEL our root view dispatches on the next DOWN. Gesture Handler's own RefreshControl avoids this because its handler drives the SwipeRefreshLayout in touch-drag mode, which finishes the spinner in onTouchEvent without relying on the nested-scroll cleanup.

The handler now calls stopNestedScroll() on the view after feeding it the final UP or the synthetic CANCEL, mirroring View.dispatchTouchEvent. It only does so while active, since below that the view still receives the events through regular dispatch, and only for views whose hook opts in through the new shouldStopNestedScroll(). ScrollViewHook opts in.

Fixes #4485

Test plan

  • Pulled to refresh repeatedly on GH ScrollView + RN RefreshControl, GH FlatList + RN RefreshControl, and Gesture.Native() around an RN ScrollView + RN RefreshControl. onRefresh fires on every pull and the spinner retracts.
  • GH ScrollView + GH RefreshControl and RN ScrollView + RN RefreshControl unchanged.
  • Nested GH ScrollView inside GH ScrollView, with and without an RN RefreshControl on the outer: scroll handover, fling, and pull-to-refresh from inside the inner list work the same as with RN scroll views.
Repro
import React, { useCallback, useState } from 'react';
import { RefreshControl, Text, View } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';

export default function App() {
  const [refreshing, setRefreshing] = useState(false);
  const onRefresh = useCallback(() => {
    setRefreshing(true);
    setTimeout(() => setRefreshing(false), 1000);
  }, []);

  return (
    <ScrollView
      style={{ flex: 1 }}
      refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}>
      {Array.from({ length: 40 }, (_, i) => (
        <View key={i} style={{ padding: 20 }}>
          <Text>Row {i}</Text>
        </View>
      ))}
    </ScrollView>
  );
}

Copilot AI lite review requested due to automatic review settings September 7, 2026 13:14
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Team

Run ID: 53c1c686-0d58-48fd-8a52-47a63dc0d3d1

📥 Commits

Reviewing files that changed from the base of the PR and between ebc923b and c139fc0.

📒 Files selected for processing (1)
  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Fixed nested scrolling behavior when gestures end or are cancelled.
    • Parent containers now receive the appropriate scroll-stop notification, allowing refresh interactions to trigger correctly.

Walkthrough

The Android native view gesture handler now conditionally stops nested scrolling when an active gesture ends or is cancelled. ScrollViewHook enables this behavior through a new hook method.

Changes

Nested scroll lifecycle

Layer / File(s) Summary
Hook policy and ScrollView opt-in
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt
NativeViewGestureHandlerHook defines shouldStopNestedScroll() with a default value of false. ScrollViewHook overrides it with true.
Gesture end and cancellation handling
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt
Active gestures call stopNestedScroll() after ACTION_UP and before cancellation events are recycled when the hook enables the behavior.

Sequence Diagram(s)

sequenceDiagram
  participant NativeViewGestureHandler
  participant ScrollViewHook
  participant ScrollView
  NativeViewGestureHandler->>ScrollViewHook: shouldStopNestedScroll()
  ScrollViewHook-->>NativeViewGestureHandler: true
  NativeViewGestureHandler->>ScrollView: stopNestedScroll() on ACTION_UP or cancellation
Loading

Suggested reviewers: j-piasecki

Merge Risk: ⚪ Minimal · up to c139f

Android ScrollView and FlatList pull-to-refresh gestures now release nested scrolling when they end or are cancelled, allowing RefreshControl to complete normally. The change is scoped to opted-in ScrollView handling and is ready to merge.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary Android change: stopping nested scrolling when a native handler gesture ends.
Linked Issues check ✅ Passed The changes address issue [#4485] by stopping nested scrolling for active native gestures and enabling the behavior for ScrollViewHook. This supports React Native RefreshControl release and pull-to-re…
Out of Scope Changes check ✅ Passed The changes are limited to native gesture-handler nested-scroll cleanup and the hook opt-in required for ScrollView. No unrelated changes are described.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The change is narrowly scoped, opt-in via hooks, and matches Android’s expected nested-scroll cleanup timing for end-of-gesture paths.

Pull request overview

This PR fixes an Android-specific interaction bug where ScrollView/FlatList from RNGH used with React Native’s RefreshControl can leave nested scrolling “open” after the gesture ends, preventing SwipeRefreshLayout from receiving onStopNestedScroll and thus delaying/losing onRefresh.

Changes:

  • Stop nested scrolling (View.stopNestedScroll()) after delivering the terminal ACTION_UP to the native view when the NativeViewGestureHandler is active and the view’s hook opts in.
  • Also stop nested scrolling after delivering the synthetic ACTION_CANCEL when the handler is cancelled/failed while active.
  • Introduce a new hook opt-in (NativeViewGestureHandlerHook.shouldStopNestedScroll()), enabled for ScrollViewHook.
File summaries
File Description
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt Adds an opt-in path to mirror View.dispatchTouchEvent cleanup by calling stopNestedScroll() at the end of an active native-view gesture (notably for ScrollViews).
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@m-bert
m-bert requested a review from j-piasecki September 7, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ScrollView/FlatList silently disable pull-to-refresh when given React Native's RefreshControl

3 participants