From 8f831f60b2b4f432e9875b41450a4d585c04e93f Mon Sep 17 00:00:00 2001 From: Ahmed Al Amawi Date: Sun, 2 Aug 2026 13:20:06 -0400 Subject: [PATCH 1/2] fix: defer focused input layout sync out of synchronous onStart dispatch --- .../listeners/KeyboardAnimationCallback.kt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt index dc4e1d0491..6d3412c831 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt @@ -217,13 +217,12 @@ class KeyboardAnimationCallback( this.persistentKeyboardHeight = keyboardHeight } - layoutObserver?.syncUpLayout() - // keyboard gets resized - we do not want to have a default animated transition // so we skip these animations val isKeyboardResized = keyboardHeight != 0.0 && prevKeyboardHeight != keyboardHeight val isKeyboardShown = isKeyboardVisible && prevKeyboardHeight != 0.0 if (isKeyboardResized && isKeyboardShown && isResizeHandledInCallbackMethods) { + layoutObserver?.syncUpLayout() onKeyboardResized(keyboardHeight) animationsToSkip.add(animation) @@ -477,6 +476,15 @@ class KeyboardAnimationCallback( val event = pendingStartEvent ?: return pendingStartEvent = null + // `FocusedInputLayoutChangedEvent` is delivered to Reanimated synchronously + // on the UI thread, just like the start event below. Dispatching it from + // within `onStart` runs inside `dispatchWindowInsetsAnimationStart`, where + // a synchronous Fabric mutation can reentrantly cancel a pending IME insets + // controller before AOSP calls `listener.onReady` (AOSP checks `isCancelled` + // only before dispatching `onStart` and never re-checks it afterwards), so + // the layout sync is flushed here together with the start event (keeping + // the original layout -> start dispatch order). + layoutObserver?.syncUpLayout() context.dispatchEvent( eventPropagationView.id, KeyboardTransitionEvent( From 07e67523ce5d6bddd37ec552389b952ca0144647 Mon Sep 17 00:00:00 2001 From: Ahmed Al Amawi Date: Mon, 3 Aug 2026 09:24:47 -0400 Subject: [PATCH 2/2] refactor: move layout sync into onKeyboardResized, remove verbose comment --- .../listeners/KeyboardAnimationCallback.kt | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt index 6d3412c831..6eb3817789 100644 --- a/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt +++ b/android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt @@ -168,7 +168,6 @@ class KeyboardAnimationCallback( if (isKeyboardFullyVisible && !isKeyboardSizeEqual && !isResizeHandledInCallbackMethods) { Logger.i(TAG, "onApplyWindowInsets: ${this.persistentKeyboardHeight} -> $keyboardHeight") - layoutObserver?.syncUpLayout() this.onKeyboardResized(keyboardHeight) return insets @@ -222,7 +221,6 @@ class KeyboardAnimationCallback( val isKeyboardResized = keyboardHeight != 0.0 && prevKeyboardHeight != keyboardHeight val isKeyboardShown = isKeyboardVisible && prevKeyboardHeight != 0.0 if (isKeyboardResized && isKeyboardShown && isResizeHandledInCallbackMethods) { - layoutObserver?.syncUpLayout() onKeyboardResized(keyboardHeight) animationsToSkip.add(animation) @@ -426,6 +424,8 @@ class KeyboardAnimationCallback( private fun onKeyboardResized(keyboardHeight: Double) { duration = 0 + layoutObserver?.syncUpLayout() + context.emitEvent("KeyboardController::keyboardWillShow", getEventParams(keyboardHeight)) listOf( KeyboardTransitionEvent.Start, @@ -476,14 +476,6 @@ class KeyboardAnimationCallback( val event = pendingStartEvent ?: return pendingStartEvent = null - // `FocusedInputLayoutChangedEvent` is delivered to Reanimated synchronously - // on the UI thread, just like the start event below. Dispatching it from - // within `onStart` runs inside `dispatchWindowInsetsAnimationStart`, where - // a synchronous Fabric mutation can reentrantly cancel a pending IME insets - // controller before AOSP calls `listener.onReady` (AOSP checks `isCancelled` - // only before dispatching `onStart` and never re-checks it afterwards), so - // the layout sync is flushed here together with the start event (keeping - // the original layout -> start dispatch order). layoutObserver?.syncUpLayout() context.dispatchEvent( eventPropagationView.id,