Fixed crash in ReactTextInputLocalData

Summary:
Motivation:

(SUDDENLY) There is a thing on Android called SpanWather, and their purpose is to notify "the watcher" about span-related changes in SpannableString. The idea is: some special kind of span can have some logic to prevent or tweak interleaving with some another kind of spans. To do so, it has to implement SpanWather interface.
So, EditText uses this to control internal spannable object (!) and SUDDENLY (#><) calls internal "layout" method as a reaction to adding new spans. So, when we are cloning SpannableString, we are (re)applying same span objects to a new spannable instance, and it causes notifying other spans in the string, and they notify EditText, and the EditText does relayout and... BOOM!
So, the solution is, easy, we should use SpannableStringBuilder instead of SpannableString because it does not notify SpanWather during cloning.

See:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#101
(the first argument is `false`).
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringBuilder.java#678
Compare with:
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/text/SpannableStringInternal.java#43

Why? I believe because SpannableStringBuilder objects are "unfinished" by design, and documentation said: "it is the caller's responsibility to restore invariants [among spans]". As we do an exact clone of the string, that's perfectly okay to assume that all invariants were already satisfied for original string.

Reviewed By: achen1

Differential Revision: D5970940

fbshipit-source-id: 590ca0e3aede4470b809c7db527c5d55ddf5edb4
This commit is contained in:
Valentin Shergin 2017-10-03 17:19:18 -07:00 committed by Facebook Github Bot
parent e1fb6fffb6
commit cdea3c574b
1 changed files with 3 additions and 3 deletions

View File

@ -10,14 +10,14 @@
package com.facebook.react.views.textinput;
import android.os.Build;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.util.TypedValue;
import android.widget.EditText;
/** Local state bearer for EditText instance. */
public final class ReactTextInputLocalData {
private final SpannableString mText;
private final SpannableStringBuilder mText;
private final float mTextSize;
private final int mMinLines;
private final int mMaxLines;
@ -25,7 +25,7 @@ public final class ReactTextInputLocalData {
private final int mBreakStrategy;
public ReactTextInputLocalData(EditText editText) {
mText = new SpannableString(editText.getText());
mText = new SpannableStringBuilder(editText.getText());
mTextSize = editText.getTextSize();
mMinLines = editText.getMinLines();
mMaxLines = editText.getMaxLines();