Skip to content

Commit 3f29f57

Browse files
zielinskimzfacebook-github-bot
authored andcommitted
Attempt to fix OOBE in TextInput component
Summary: There is an old crash that we have in TextInput component: https://fburl.com/logview/jr4x3yvb and we're observing it in the primitive TextInput as well: https://fburl.com/logview/aap0bkxp In this diff I'm trying to fix that crash by making a copy of the text before we start measuring it. Reviewed By: apowolny Differential Revision: D74305019 fbshipit-source-id: e6e16183500dfbd11d91ea5848b94a81f37ffca1
1 parent e2bdfd2 commit 3f29f57

File tree

2 files changed

+67
-11
lines changed

2 files changed

+67
-11
lines changed

litho-core/src/main/java/com/facebook/litho/config/ComponentsConfiguration.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,9 @@ internal constructor(
298298
/** This flag is to enable custom Context for TextInput measurement */
299299
@JvmField var useCustomContextForTextInputMeasurement: Boolean = false
300300

301+
/** This flag is to enable making a copy of the text in TextInput component */
302+
@JvmField var copyInitialTextInTextInputRender: Boolean = false
303+
301304
/**
302305
* This flag is used to enable A11Y support for keyboard navigation, which was disabled due to
303306
* Litho's A11Y sanity check.

litho-widget/src/main/java/com/facebook/litho/widget/ExperimentalTextInput.kt

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ import android.widget.EditText
5050
import android.widget.TextView
5151
import androidx.annotation.ColorInt
5252
import androidx.annotation.GravityInt
53-
import androidx.core.text.toSpanned
5453
import androidx.core.util.ObjectsCompat
5554
import androidx.core.view.ViewCompat
5655
import com.facebook.litho.ComponentContext
5756
import com.facebook.litho.ComponentScope
5857
import com.facebook.litho.LithoPrimitive
5958
import com.facebook.litho.PrimitiveComponent
6059
import com.facebook.litho.PrimitiveComponentScope
60+
import com.facebook.litho.State
6161
import com.facebook.litho.Style
6262
import com.facebook.litho.ThreadUtils.assertMainThread
6363
import com.facebook.litho.annotations.Hook
@@ -237,7 +237,20 @@ class ExperimentalTextInput(
237237
override fun PrimitiveComponentScope.render(): LithoPrimitive {
238238
val mountedView = useState { AtomicReference<EditTextWithEventHandlers?>() }
239239
val savedText = useState { AtomicReference(initialText) }
240+
val copyInitialTextInTextInputRender =
241+
useState { ComponentsConfiguration.copyInitialTextInTextInputRender }.value
242+
243+
// control
240244
val measureSeqNumber = useState { 0 }
245+
// test
246+
val textForMeasure =
247+
useState<CharSequence> {
248+
if (copyInitialTextInTextInputRender) {
249+
initialText.toString()
250+
} else {
251+
initialText
252+
}
253+
}
241254

242255
val resolvedHighlightColor = useCached {
243256
if (highlightColor != null) {
@@ -289,6 +302,8 @@ class ExperimentalTextInput(
289302
movementMethod = movementMethod,
290303
savedText = savedText.value,
291304
measureSeqNumber = measureSeqNumber.value,
305+
copyInitialTextInTextInputRender = copyInitialTextInTextInputRender,
306+
textForMeasure = textForMeasure,
292307
),
293308
mountBehavior =
294309
MountBehavior(
@@ -304,10 +319,20 @@ class ExperimentalTextInput(
304319

305320
// Controller
306321
withDescription("text-input-controller") {
307-
bind(textInputController, savedText.value, measureSeqNumber) { editText ->
308-
textInputController?.bind(editText, savedText.value, measureSeqNumber)
309-
onUnbind { textInputController?.unbind() }
310-
}
322+
bind(
323+
textInputController,
324+
savedText.value,
325+
measureSeqNumber,
326+
copyInitialTextInTextInputRender,
327+
textForMeasure) { editText ->
328+
textInputController?.bind(
329+
editText,
330+
savedText.value,
331+
measureSeqNumber,
332+
copyInitialTextInTextInputRender,
333+
textForMeasure)
334+
onUnbind { textInputController?.unbind() }
335+
}
311336
}
312337
// OnMount
313338
withDescription("text-input-equivalent-mount") {
@@ -423,6 +448,8 @@ class ExperimentalTextInput(
423448
editText.onInputConnection = onInputConnection
424449
editText.onTextPasted = onTextPasted
425450
editText.measureSeqNumber = measureSeqNumber
451+
editText.copyInitialTextInTextInputRender = copyInitialTextInTextInputRender
452+
editText.textForMeasure = textForMeasure
426453

427454
onUnbind {
428455
editText.detachWatchers()
@@ -438,6 +465,8 @@ class ExperimentalTextInput(
438465
editText.customInsertionActionModeCallback = null
439466
editText.onTextPasted = null
440467
editText.measureSeqNumber = null
468+
editText.copyInitialTextInTextInputRender = false
469+
editText.textForMeasure = null
441470
}
442471
}
443472
}
@@ -482,6 +511,8 @@ internal class TextInputLayoutBehavior(
482511
private val savedText: AtomicReference<CharSequence?>,
483512
// we're only reading it here in order to force remeasure if it gets updated
484513
private val measureSeqNumber: Int,
514+
private val copyInitialTextInTextInputRender: Boolean,
515+
private val textForMeasure: State<CharSequence>,
485516
) : LayoutBehavior {
486517
override fun LayoutScope.layout(sizeConstraints: SizeConstraints): PrimitiveLayoutResult {
487518
val context =
@@ -528,7 +559,9 @@ internal class TextInputLayoutBehavior(
528559
// onMeasure happens:
529560
// 1. After initState before onMount: savedText = initText.
530561
// 2. After onMount before onUnmount: savedText preserved from underlying editText.
531-
savedText.get())
562+
savedText.get(),
563+
copyInitialTextInTextInputRender,
564+
textForMeasure)
532565

533566
return PrimitiveLayoutResult(
534567
height = forMeasure.measuredHeight,
@@ -588,14 +621,18 @@ fun createAndMeasureEditText(
588621
importantForAutofill: Int,
589622
autofillHints: Array<String?>?,
590623
disableAutofill: Boolean,
591-
text: CharSequence?
624+
text: CharSequence?,
625+
copyInitialTextInTextInputRender: Boolean,
626+
textForMeasure: State<CharSequence>,
592627
): EditText {
593628
// The height should be the measured height of EditText with relevant params
594629
var textToMeasure = text
595630
val forMeasure = ForMeasureEditText(context)
596631
// If text contains Spans, we don't want it to be mutable for the measurement case
597-
if (textToMeasure is Spannable) {
598-
textToMeasure = textToMeasure.toSpanned()
632+
if (copyInitialTextInTextInputRender) {
633+
textToMeasure = textForMeasure.value
634+
} else if (textToMeasure is Spannable) {
635+
textToMeasure = textToMeasure.toString()
599636
}
600637
if (context is MeasureContext) {
601638
context.withInputMethodManagerDisabled {
@@ -868,6 +905,8 @@ internal class EditTextWithEventHandlers(context: Context?) :
868905
var onInputConnection: ((InputConnection?, EditorInfo) -> InputConnection)? = null
869906
var componentContext: ComponentContext? = null
870907
var measureSeqNumber: com.facebook.litho.State<Int>? = null
908+
var copyInitialTextInTextInputRender: Boolean = false
909+
var textForMeasure: com.facebook.litho.State<CharSequence>? = null
871910
private var textState: AtomicReference<CharSequence?>? = null
872911
private var textLineCount = UNMEASURED_LINE_COUNT
873912
private var textWatcher: TextWatcher? = null
@@ -898,7 +937,11 @@ internal class EditTextWithEventHandlers(context: Context?) :
898937
if (this.textLineCount != UNMEASURED_LINE_COUNT &&
899938
(this.textLineCount != lineCount) &&
900939
(componentContext != null)) {
901-
measureSeqNumber?.update { it + 1 }
940+
if (copyInitialTextInTextInputRender) {
941+
textForMeasure?.update(text.toString())
942+
} else {
943+
measureSeqNumber?.update { it + 1 }
944+
}
902945
}
903946
}
904947

@@ -1189,15 +1232,21 @@ class TextInputController internal constructor() {
11891232
private var editText: EditTextWithEventHandlers? = null
11901233
private var savedText: AtomicReference<CharSequence?>? = null
11911234
private var measureSeqNumber: com.facebook.litho.State<Int>? = null
1235+
private var copyInitialTextInTextInputRender: Boolean = false
1236+
private var textForMeasure: com.facebook.litho.State<CharSequence>? = null
11921237

11931238
internal fun bind(
11941239
editText: EditTextWithEventHandlers,
11951240
savedText: AtomicReference<CharSequence?>,
11961241
measureSeqNumber: com.facebook.litho.State<Int>?,
1242+
copyInitialTextInTextInputRender: Boolean,
1243+
textForMeasure: com.facebook.litho.State<CharSequence>?,
11971244
) {
11981245
this.editText = editText
11991246
this.savedText = savedText
12001247
this.measureSeqNumber = measureSeqNumber
1248+
this.copyInitialTextInTextInputRender = copyInitialTextInTextInputRender
1249+
this.textForMeasure = textForMeasure
12011250
}
12021251

12031252
internal fun unbind() {
@@ -1335,7 +1384,11 @@ class TextInputController internal constructor() {
13351384
}
13361385

13371386
private fun remeasureForUpdatedTextSync() {
1338-
measureSeqNumber?.updateSync { it + 1 }
1387+
if (copyInitialTextInTextInputRender) {
1388+
textForMeasure?.updateSync(savedText?.get().toString())
1389+
} else {
1390+
measureSeqNumber?.updateSync { it + 1 }
1391+
}
13391392
}
13401393
}
13411394

0 commit comments

Comments
 (0)