-
Notifications
You must be signed in to change notification settings - Fork 24.8k
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
FlatList
render item with aText
that has itsselectable
property 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