From b8f5cf5f731b830d34321207ddf5aedddacd9224 Mon Sep 17 00:00:00 2001 From: bintangakbarRK Date: Tue, 4 Aug 2026 14:31:07 +0700 Subject: [PATCH] fix(TextInput): report correct selection after autofill on Android Move the onChange event dispatch from onTextChanged to afterTextChanged. During system autofill, Android sets the text content before updating the cursor position. When onTextChanged fires, editText.selectionStart/End still report {0, 0} from the initial empty state. By the time afterTextChanged runs, the Editable is fully committed and Android has updated the selection to reflect the actual cursor position. This fixes the regression where autofill produces selection: {start: 0, end: 0} instead of the correct position at the end of the inserted text. Paste and keyboard input are unaffected since Android updates their selection synchronously before the TextWatcher callbacks. Fixes #57458 --- .../textinput/ReactTextInputTextWatcher.kt | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt index e94f841ffb9..b4a40cfbf97 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt @@ -22,6 +22,7 @@ internal class ReactTextInputTextWatcher( private val eventDispatcher: EventDispatcher? = UIManagerHelper.getEventDispatcher(reactContext) private val surfaceId = UIManagerHelper.getSurfaceId(reactContext) private var previousText: String? = null + private var shouldDispatchChange: Boolean = false override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { // Incoming charSequence gets mutated before onTextChanged() is invoked @@ -46,6 +47,15 @@ internal class ReactTextInputTextWatcher( return } + shouldDispatchChange = true + } + + override fun afterTextChanged(s: Editable) { + if (!shouldDispatchChange) { + return + } + shouldDispatchChange = false + val stateWrapper = editText.stateWrapper if (stateWrapper != null) { @@ -55,7 +65,10 @@ internal class ReactTextInputTextWatcher( stateWrapper.updateState(newStateData) } - // The event that contains the event counter and updates it must be sent first. + // Dispatch in afterTextChanged so that the selection reflects the final cursor + // position. During autofill, onTextChanged fires before Android updates the + // selection, causing {start: 0, end: 0} to be reported instead of the actual + // cursor position. eventDispatcher?.dispatchEvent( ReactTextChangedEvent( surfaceId, @@ -67,6 +80,4 @@ internal class ReactTextInputTextWatcher( ), ) } - - override fun afterTextChanged(s: Editable) = Unit }