From cdea3c574ba698b5111ac3c78c0b0984e20b4df2 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Tue, 3 Oct 2017 17:19:18 -0700 Subject: [PATCH] Fixed crash in ReactTextInputLocalData MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../react/views/textinput/ReactTextInputLocalData.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java index 1740a6800..f12cf8832 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputLocalData.java @@ -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();