fix(android): resolve enclosing scroll view when useNestedScrollViewAndroid is enabled - #1537
Open
debitan wants to merge 1 commit into
Open
fix(android): resolve enclosing scroll view when useNestedScrollViewAndroid is enabled#1537debitan wants to merge 1 commit into
debitan wants to merge 1 commit into
Conversation
…ndroid is enabled
kirillzyusko
self-requested a review
July 9, 2026 14:33
kirillzyusko
reviewed
Jul 9, 2026
Comment on lines
+109
to
+111
| if ((parentView is ReactScrollView && parentView.scrollEnabled) || | ||
| parentView is NestedScrollView | ||
| ) { |
Owner
There was a problem hiding this comment.
While I like these changes, we miss one important thing - if ScrollView will be with disabled scroll then this code still return it as valid scrollable container.
Do you think we can add extension that can handle both cases?
import android.view.View
import com.facebook.react.views.scroll.ReactScrollView
import java.lang.reflect.Field
import java.lang.reflect.Method
internal fun View.isEnabledReactScrollView(): Boolean {
if (this is ReactScrollView) {
return scrollEnabled
}
if (ReactNestedScrollViewCompat.isInstance(this)) {
return ReactNestedScrollViewCompat.isScrollEnabled(this)
}
return false
}
private object ReactNestedScrollViewCompat {
private const val CLASS_NAME =
"com.facebook.react.views.scroll.ReactNestedScrollView"
private val clazz: Class<*>? by lazy(LazyThreadSafetyMode.PUBLICATION) {
runCatching {
Class.forName(
CLASS_NAME,
false,
ReactScrollView::class.java.classLoader,
)
}.getOrNull()
}
private val scrollEnabledGetter: Method? by lazy(LazyThreadSafetyMode.PUBLICATION) {
val c = clazz ?: return@lazy null
runCatching {
c.getMethod("getScrollEnabled")
}.getOrNull()
?: runCatching {
c.getMethod("isScrollEnabled")
}.getOrNull()
}
private val scrollEnabledField: Field? by lazy(LazyThreadSafetyMode.PUBLICATION) {
val c = clazz ?: return@lazy null
runCatching {
c.getDeclaredField("mScrollEnabled").apply {
isAccessible = true
}
}.getOrNull()
}
fun isInstance(view: View): Boolean {
return clazz?.isInstance(view) == true
}
fun isScrollEnabled(view: View): Boolean {
scrollEnabledGetter?.let { getter ->
return runCatching {
getter.invoke(view) as Boolean
}.getOrDefault(false)
}
scrollEnabledField?.let { field ->
return runCatching {
field.getBoolean(view)
}.getOrDefault(false)
}
return false
}
}And use it like:
if (parentView.isEnabledReactScrollView()) {I'm fine if you can simpler/better code. My main concern is that we need to check scrollEnabled property too.
Ideally would be also to check that it returns false with disabled ScrollView + check any possible performance impact. The reflection is an expensive thing and calling it for each view might be expensive 🤞
Let me know what do you think about it!
Contributor
📊 Package size report
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
EditText.parentScrollViewTargetonly matchesReactScrollView, but when theuseNestedScrollViewAndroidfeature flag is enabled RN renders the vertical scroll view asReactNestedScrollView. The walk therefore never finds the enclosing scroll view and returns-1, soKeyboardAwareScrollView'smaybeScrollbails on theparentScrollViewTarget !== scrollViewTargetguard and the focused input is never scrolled above the keyboard.ReactNestedScrollViewis package-private, so it cannot be referenced by type from outsidecom.facebook.react.views.scroll. This matches its public superclassandroidx.core.widget.NestedScrollViewinstead (whichReactNestedScrollViewextends).💡 Motivation and Context
Fixes the Android auto-scroll failure discussed in #1411 for apps that enable
useNestedScrollViewAndroid(commonly turned on for nested scrolling inside native bottom sheets). With the flag on,KeyboardAwareScrollViewstopped scrolling focused inputs into view on Android.📢 Changelog
Android
NestedScrollView(the public superclass ofReactNestedScrollView) inparentScrollViewTarget, so auto-scroll works when theuseNestedScrollViewAndroidfeature flag is enabled.🤔 How Has This Been Tested?
Tested in a React Native 0.85 (New Architecture) app on a physical Pixel and an emulator with
useNestedScrollViewAndroidenabled. Before: inputs near the bottom of aKeyboardAwareScrollViewwere covered by the keyboard, andparentScrollViewTargetlogged as-1. After: focused inputs scroll above the keyboard as expected. iOS is unaffected.📝 Checklist