-
Notifications
You must be signed in to change notification settings - Fork 25k
Description
Description
Summary
Text components rendered within the confines of a FlatList are not selectable even when the selectable property is set to true. Setting the removeClippedSubviews to false has a negative performance impact.
This bug can likely be attributed to the bug captured in Google's Issue Tracker here. I was able to override our RCTText manager and ReactTextView implementation with a subclass that performs the suggested fix here, and the views immediately became selectable.
Citations
Previous issues:
#12342
#14746
#26264
#27107
Previous fix:
#28952
Proposed Fix
Per the suggestion I linked above, a quick toggle of the textIsSelectable flag works just fine. Here's my implementation for a subclass in Kotlin:
internal class SelectableReactTextView(context: Context) : ReactTextView(context) {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (isTextSelectable) {
setTextIsSelectable(false)
setTextIsSelectable(true)
}
}
}I propose that this toggle of the textIsSelectable property be added directly to the ReactTextView class in its onAttachedToWindow override. This fix is similar in spirit to the one in the "Previous fix" section above.
It's important to note that the textIsSelectable property is correctly set, alongside the focusable, focusableInTouchMode, clickable, and longClickable properties. The underlying recycler view simply does not honor these settings.
Underlying cause
The Editor class, responsible for enabling text selection in Android, during its prepareCursorControllers phase attempts to access the root view from the TextView. On normal text views (i.e. not mounted in a FlatList), the root view is a DecorView with layout parameters. On text views mounted by a FlatList, the root view is a ReactViewGroup with null layout parameters. Subsequently, the check to ensure that the window can handle selections fails and the Editor sets its internal mSelectionControllerEnabled property to false.
The Editor has a performLongClick handler where normal selection events would fire. Towards the tail end of the function's implementation, it tries to call selectCurrentWordAndStartDrag. The third check, checkField, returns false because its call to canSelectText returns false. The canSelectText function is checking for that aforementioned mSelectionControllerEnabled property. This is what causes the debug warning to fire "TextView does not support text selection. Selection cancelled."
So why does firing setTextIsSelectable first with false, then true, work? There's a check within every TextView that sees if it's internal reference to the Editor mEditor and the given selectable property match. Sure enough, they do! So the function returns early and doesn't execute mEditor.prepareCursorControllers. But by flipping the selectable property to false, we end up falling through the entire function to execute prepareCursorControllers. By this point, we know the view has properly been attached to a window and not to the ephemeral RecyclerView, so executing that function should re-enable the selection controller.
It's the same reason why the solution linked in the Google tracker uses setEnabled instead of setTextIsSelectable--that function also executes prepareCursorControllers. We could call setText too, or setMovementMode, but setTextIsSelectable felt like the leanest, safest way to do it.
React Native Version
0.70.4
Output of npx react-native info
We have a pretty custom setup where our NPM packages are managed internally, but here's my device:
System:
OS: macOS 13.2.1
CPU: (10) arm64 Apple M1 Max
Memory: 339.73 MB / 64.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 19.5.0 - /opt/homebrew/bin/node
Yarn: 1.22.19 - /opt/homebrew/bin/yarn
npm: 9.3.1 - /opt/homebrew/bin/npm
Watchman: 2023.04.10.00 - /opt/homebrew/bin/watchman
Managers:
CocoaPods: Not Found
SDKs:
iOS SDK:
Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4
Android SDK: Not Found
IDEs:
Android Studio: 2022.2 AI-222.4459.24.2221.9862592
Xcode: 14.3/14E222b - /usr/bin/xcodebuild
Languages:
Java: 11.0.16.1 - /usr/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: Not Found
react-native: 0.69.8 => 0.69.8
react-native-macos: Not Found
npmGlobalPackages:
*react-native*: Not Found
Steps to reproduce
- Build a
FlatListrender item with aTextthat has itsselectableproperty set totrue - Try to select the text on Android, and fail with a warning:
TextView does not support text selection. Selection cancelled.
Snack, code example, screenshot, or link to a repository
https://snack.expo.dev/@abbondanzo/react-native-android---text-not-selectable-in-flatlist
Here's a quick demo of the warnings that are thrown from the native platform using the code from the Snack linked above:
https://user-images.githubusercontent.com/10366495/234964124-1bbecae9-a6a0-48f7-9d34-858a5c5be460.mov