Skip to content

Commit 97aa7ad

Browse files
shahidrogersmeta-codesync[bot]
authored andcommitted
Fix ClassCastException in clearFocusAndMaybeRefocus when EditText is detached (#57423)
Summary: On Android 9 and below (`SDK_INT <= P`) in touch mode, `ReactEditText.clearFocusAndMaybeRefocus()` unconditionally casts `rootView` to `ViewGroup`: ```kotlin val rootViewGroup = rootView as ViewGroup ``` `View.getRootView()` returns the view **itself** when the view is detached from the window. An IME editor action is delivered asynchronously over Binder (`IInputConnectionWrapper`), so a submit-key press can arrive after the EditText has already been removed from the hierarchy (screen unmount/navigation racing the keyboard). When that happens the cast throws and kills the app: ``` java.lang.ClassCastException: com.facebook.react.views.textinput.ReactEditText cannot be cast to android.view.ViewGroup at com.facebook.react.views.textinput.ReactEditText.clearFocusAndMaybeRefocus (ReactEditText.kt:379) at com.facebook.react.views.textinput.ReactTextInputManager.addEventEmitters$lambda$3 (ReactTextInputManager.kt:933) at android.widget.TextView.onEditorAction (TextView.java:6615) at com.android.internal.widget.EditableInputConnection.performEditorAction (EditableInputConnection.java:138) at android.view.inputmethod.InputConnectionWrapper.performEditorAction (InputConnectionWrapper.java:190) at com.android.internal.view.IInputConnectionWrapper.executeMessage (IInputConnectionWrapper.java:360) ``` We see this steadily in production Crashlytics (RN 0.86, New Architecture): all events are on Android 7–9 devices (Samsung SM-J710GN / SM-G610F on 8.1.0, etc.), zero on Android 10+, because API > 28 takes the plain `super.clearFocus()` branch and never reaches the cast. This change replaces the unchecked cast with a safe cast and falls back to a plain `clearFocus()` when the root is not a `ViewGroup`. That fallback is correct because the only reason the root isn't a `ViewGroup` is that the view is already detached — there is no surviving focus hierarchy to protect with the `descendantFocusability` workaround, and `hideSoftKeyboard()` still runs afterwards. Behavior is unchanged on API > 28, in non-touch mode, and in the normal attached case on old Android. ## Changelog: [ANDROID] [FIXED] - Fix ClassCastException crash on Android 9 and below when an IME submit action races the unmount of a TextInput Pull Request resolved: #57423 Test Plan: The race is timing-dependent, so it is exercised by the scenario rather than a unit test: 1. On an API 26–28 emulator/device, render a single-line `<TextInput>` with default `submitBehavior` (`blurAndSubmit`), focused, keyboard open. 2. Unmount the input (conditional render / navigation) in the same frame as pressing the keyboard's submit key. The IME action arrives over Binder after the view is detached. 3. Before this change: `getRootView()` returns the detached `ReactEditText` itself → `ClassCastException` (stack above). After this change: the safe cast falls back to `super.clearFocus()` + `hideSoftKeyboard()`, no crash, no behavioral difference otherwise. Also verified: - On the attached path (normal blur-on-submit on API ≤ 28), `rootView` is the DecorView, the safe cast succeeds, and the existing descendant-focusability logic runs exactly as before. - On API > 28 the first branch is taken, unchanged. Reviewed By: cortinico Differential Revision: D110567654 Pulled By: javache fbshipit-source-id: eb580aed7d0d029eb265b815ba7bed5e5ac85b4c
1 parent c65845c commit 97aa7ad

1 file changed

Lines changed: 14 additions & 5 deletions

File tree

  • packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -380,11 +380,20 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
380380
// Avoid refocusing to a new view on old versions of Android by default
381381
// by preventing `requestFocus()` on the rootView from moving focus to any child.
382382
// https://cs.android.com/android/_/android/platform/frameworks/base/+/bdc66cb5a0ef513f4306edf9156cc978b08e06e4
383-
val rootViewGroup = rootView as ViewGroup
384-
val oldDescendantFocusability = rootViewGroup.descendantFocusability
385-
rootViewGroup.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
386-
super.clearFocus()
387-
rootViewGroup.descendantFocusability = oldDescendantFocusability
383+
//
384+
// getRootView() returns the view itself when it is detached from the window, so the root
385+
// is not necessarily a ViewGroup: an IME editor action delivered over Binder can race the
386+
// removal of this view from the hierarchy. There is no focus to move in that case, so a
387+
// plain clearFocus() is enough.
388+
val rootViewGroup = rootView as? ViewGroup
389+
if (rootViewGroup != null) {
390+
val oldDescendantFocusability = rootViewGroup.descendantFocusability
391+
rootViewGroup.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
392+
super.clearFocus()
393+
rootViewGroup.descendantFocusability = oldDescendantFocusability
394+
} else {
395+
super.clearFocus()
396+
}
388397
}
389398
hideSoftKeyboard()
390399
}

0 commit comments

Comments
 (0)