From c360b1d92b69e1d298b390ec88c4d29c1023945a Mon Sep 17 00:00:00 2001 From: fabriziobertoglio1987 Date: Wed, 1 Sep 2021 14:50:38 -0700 Subject: [PATCH] Fix non selectable Text in FlatList (#28952) Summary: This issue fixes https://github.com/facebook/react-native/issues/26264 fixes https://github.com/facebook/react-native/issues/27107 Text is not selectable inside a FlatList on Android. The solution is to invalidate the ReactTextView after a change of the selectable prop. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future and make the Text selectable. ## Changelog [Android] [Fixed] - Fix non selectable Text in FlatList Pull Request resolved: https://github.com/facebook/react-native/pull/28952 Test Plan: **
CLICK TO OPEN TESTS RESULTS**

The issue was demonstrated in the following [snack](https://snack.expo.io/fabrizio.bertoglio/selectable-bug-in-flatlist) (more info in issue https://github.com/facebook/react-native/issues/26264). The solution is: 1) Calling `invalidate()` from [setSelectableText][1] after changing the `selectable` prop and `mSelectableText` value. [`invalidate()`](https://developer.android.com/reference/android/view/View#invalidate()) triggers the `onDraw` callback. [1]: https://github.com/fabriziobertoglio1987/react-native/blob/8027524947cafd5cbdc492e4ef9c92b346fe23fc/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L427-L430 2) calling `setTextIsSelectable(mSelectableText);` from the [`onDraw`][2] callback [2]: https://github.com/fabriziobertoglio1987/react-native/blob/8027524947cafd5cbdc492e4ef9c92b346fe23fc/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java#L456-L460 The example below is availabe in RNTester FlatList example. Two options (`onPressDisabled` and `textSelectable`) have been added to test the functionality inside a FlatList.

Reviewed By: ShikaSD Differential Revision: D30000870 Pulled By: lunaleaps fbshipit-source-id: 4851a294960df0af057d006793aa9ba97c51e3f9 --- .../react/views/text/ReactTextView.java | 8 +++++++ .../js/components/ListExampleShared.js | 4 +++- .../js/examples/FlatList/FlatListExample.js | 23 ++++++++++++++++++- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index 7ce861de7bf..64135eda4f5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -56,6 +56,7 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie private boolean mAdjustsFontSizeToFit = false; private int mLinkifyMaskType = 0; private boolean mNotifyOnInlineViewLayout; + private boolean mTextIsSelectable = false; private ReactViewBackgroundManager mReactBackgroundManager; private Spannable mSpanned; @@ -433,9 +434,16 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie } } + @Override + public void setTextIsSelectable(boolean selectable) { + mTextIsSelectable = selectable; + super.setTextIsSelectable(selectable); + } + @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + setTextIsSelectable(mTextIsSelectable); if (mContainsImages && getText() instanceof Spanned) { Spanned text = (Spanned) getText(); TextInlineImageSpan[] spans = text.getSpans(0, text.length(), TextInlineImageSpan.class); diff --git a/packages/rn-tester/js/components/ListExampleShared.js b/packages/rn-tester/js/components/ListExampleShared.js index 2312ee69c22..158590e4ad3 100644 --- a/packages/rn-tester/js/components/ListExampleShared.js +++ b/packages/rn-tester/js/components/ListExampleShared.js @@ -57,13 +57,14 @@ class ItemComponent extends React.PureComponent<{ onPress: (key: string) => void, onShowUnderlay?: () => void, onHideUnderlay?: () => void, + textSelectable?: ?boolean, ... }> { _onPress = () => { this.props.onPress(this.props.item.key); }; render(): React.Node { - const {fixedHeight, horizontal, item} = this.props; + const {fixedHeight, horizontal, item, textSelectable} = this.props; const itemHash = Math.abs(hashCode(item.title)); const imgSource = THUMB_URLS[itemHash % THUMB_URLS.length]; return ( @@ -81,6 +82,7 @@ class ItemComponent extends React.PureComponent<{ {!item.noImage && } {item.title} - {item.text} diff --git a/packages/rn-tester/js/examples/FlatList/FlatListExample.js b/packages/rn-tester/js/examples/FlatList/FlatListExample.js index 4606d68090c..93d7eb2de4b 100644 --- a/packages/rn-tester/js/examples/FlatList/FlatListExample.js +++ b/packages/rn-tester/js/examples/FlatList/FlatListExample.js @@ -59,6 +59,8 @@ type State = {| empty: boolean, useFlatListItemComponent: boolean, fadingEdgeLength: number, + onPressDisabled: boolean, + textSelectable: boolean, |}; class FlatListExample extends React.PureComponent { @@ -74,6 +76,8 @@ class FlatListExample extends React.PureComponent { empty: false, useFlatListItemComponent: false, fadingEdgeLength: 0, + onPressDisabled: false, + textSelectable: true, }; _onChangeFilterText = filterText => { @@ -161,6 +165,16 @@ class FlatListExample extends React.PureComponent { this.state.debug, this._setBooleanValue('debug'), )} + {renderSmallSwitchOption( + 'onPress Disabled', + this.state.onPressDisabled, + this._setBooleanValue('onPressDisabled'), + )} + {renderSmallSwitchOption( + 'Text Selectable', + this.state.textSelectable, + this._setBooleanValue('textSelectable'), + )} {renderSmallSwitchOption( 'Use FlatListItemComponent', this.state.useFlatListItemComponent, @@ -236,6 +250,12 @@ class FlatListExample extends React.PureComponent { data: state.data.concat(genItemData(100, state.data.length)), })); }; + _onPressCallback = () => { + const {onPressDisabled} = this.state; + const warning = () => console.log('onPress disabled'); + const onPressAction = onPressDisabled ? warning : this._pressItem; + return onPressAction; + }; _onRefresh = () => Alert.alert('onRefresh: nothing to refresh :P'); _renderItemComponent = () => { const flatListPropKey = this.state.useFlatListItemComponent @@ -253,9 +273,10 @@ class FlatListExample extends React.PureComponent { item={item} horizontal={this.state.horizontal} fixedHeight={this.state.fixedHeight} - onPress={this._pressItem} + onPress={this._onPressCallback()} onShowUnderlay={separators.highlight} onHideUnderlay={separators.unhighlight} + textSelectable={this.state.textSelectable} /> ); },