Skip to content

Commit acbc7ae

Browse files
authored
fix: wrong onEnd event on Samsung devices when keyboard gets shown after interactive dismissal (#1240)
## 📜 Description Fixed wrong event for "end" keyboard movement after interactive dismissal (when keyboard gets shown after interaction). ## 💡 Motivation and Context Originally reported by @pioner92 months ago and later raised as an issue by @iankberry It's a known issue that in `onEnd` we may get out-of-dated keyboard frame metrics. The fix that I used earlier was postponing the event a little bit if it was interactive dismissal. It fixed the issue for my pixel device in #814 However if we test Samsung device we can clearly see that the bug is reproducible again. In this PR I'm continue to use the idea introduced in #814 and just use larger delay. It fixes the problem, but I'll conduct an exploration for a better/more synchronous code. I started this investigation in #1017 and I plan to return to this PR soon 👀 (but for now we'll merge a workaround) Closes #1238 ## 📢 Changelog <!-- High level overview of important changes --> <!-- For example: fixed status bar manipulation; added new types declarations; --> <!-- If your changes don't affect one of platform/language below - then remove this platform/language --> ### JS - added `onEnd` handler in "Interactive Keyboard" example ### Android - added `UIThread` constants; - use `.postDelayed()` instead of `.post()` with one frame delay; ## 🤔 How Has This Been Tested? Tested manually on Samsung S25+, Android 15 (Remote test lab) in example app. ## 📸 Screenshots (if appropriate): |Before|After| |-------|-----| |<video src="https://github.com/user-attachments/assets/45e63086-e49f-4b53-b4c1-647045021709">|<video src="https://github.com/user-attachments/assets/bafc21c1-49f3-4ea0-a248-1d587feedfbc">| ## 📝 Checklist - [x] CI successfully passed - [x] I added new mocks and corresponding unit-tests if library API was changed
1 parent 29afc30 commit acbc7ae

File tree

4 files changed

+54
-26
lines changed

4 files changed

+54
-26
lines changed

FabricExample/src/screens/Examples/InteractiveKeyboard/index.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,30 @@ const useKeyboardAnimation = () => {
2323
const progress = useSharedValue(0);
2424
const height = useSharedValue(0);
2525

26-
useKeyboardHandler({
27-
onMove: (e) => {
28-
"worklet";
26+
useKeyboardHandler(
27+
{
28+
onMove: (e) => {
29+
"worklet";
2930

30-
// eslint-disable-next-line react-compiler/react-compiler
31-
progress.value = e.progress;
32-
height.value = e.height;
33-
},
34-
onInteractive: (e) => {
35-
"worklet";
31+
// eslint-disable-next-line react-compiler/react-compiler
32+
progress.value = e.progress;
33+
height.value = e.height;
34+
},
35+
onInteractive: (e) => {
36+
"worklet";
37+
38+
progress.value = e.progress;
39+
height.value = e.height;
40+
},
41+
onEnd: (e) => {
42+
"worklet";
3643

37-
progress.value = e.progress;
38-
height.value = e.height;
44+
progress.value = e.progress;
45+
height.value = e.height;
46+
},
3947
},
40-
});
48+
[],
49+
);
4150

4251
return { height, progress };
4352
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.reactnativekeyboardcontroller.constants
2+
3+
import kotlin.math.floor
4+
5+
object UIThread {
6+
const val MILLISECONDS_IN_SECOND = 1000.0
7+
const val FPS = 60
8+
val NEXT_FRAME = floor(MILLISECONDS_IN_SECOND / FPS).toLong()
9+
}

android/src/main/java/com/reactnativekeyboardcontroller/listeners/KeyboardAnimationCallback.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.facebook.react.uimanager.ThemedReactContext
1414
import com.facebook.react.uimanager.UIManagerHelper
1515
import com.facebook.react.views.view.ReactViewGroup
1616
import com.reactnativekeyboardcontroller.constants.Keyboard
17+
import com.reactnativekeyboardcontroller.constants.UIThread
1718
import com.reactnativekeyboardcontroller.events.KeyboardTransitionEvent
1819
import com.reactnativekeyboardcontroller.extensions.appearance
1920
import com.reactnativekeyboardcontroller.extensions.dispatchEvent
@@ -334,8 +335,8 @@ class KeyboardAnimationCallback(
334335

335336
if (isKeyboardInteractive) {
336337
// in case of interactive keyboard we can not read keyboard frame straight away
337-
// (because we'll always read `0`), so we are posting runnable to the main thread
338-
view.post(runnable)
338+
// (because we'll always read `0`), so we are posting runnable to the next frame on the main thread
339+
view.postDelayed(runnable, UIThread.NEXT_FRAME)
339340
} else {
340341
runnable.run()
341342
}

example/src/screens/Examples/InteractiveKeyboard/index.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,30 @@ const useKeyboardAnimation = () => {
2323
const progress = useSharedValue(0);
2424
const height = useSharedValue(0);
2525

26-
useKeyboardHandler({
27-
onMove: (e) => {
28-
"worklet";
26+
useKeyboardHandler(
27+
{
28+
onMove: (e) => {
29+
"worklet";
2930

30-
// eslint-disable-next-line react-compiler/react-compiler
31-
progress.value = e.progress;
32-
height.value = e.height;
33-
},
34-
onInteractive: (e) => {
35-
"worklet";
31+
// eslint-disable-next-line react-compiler/react-compiler
32+
progress.value = e.progress;
33+
height.value = e.height;
34+
},
35+
onInteractive: (e) => {
36+
"worklet";
37+
38+
progress.value = e.progress;
39+
height.value = e.height;
40+
},
41+
onEnd: (e) => {
42+
"worklet";
3643

37-
progress.value = e.progress;
38-
height.value = e.height;
44+
progress.value = e.progress;
45+
height.value = e.height;
46+
},
3947
},
40-
});
48+
[],
49+
);
4150

4251
return { height, progress };
4352
};

0 commit comments

Comments
 (0)