Skip to content

Commit 4a6c933

Browse files
zoontekmeta-codesync[bot]
authored andcommitted
Fix Keyboard events and KeyboardAvoidingView on Android (edge-to-edge) (#55855)
Summary: `KeyboardAvoidingView` has several issues on Android 15+ and when `edgeToEdgeEnabled` is set to `true` on Android < 15: 1. **Keyboard events relied on a legacy detection path for Android < 30** (`checkForKeyboardEventsLegacy`), which used height heuristics (`heightDiff > mMinKeyboardHeightDetected`) to guess whether the keyboard was visible. 2. **`keyboardDidHide` reported incorrect `screenY` values.** It used `mVisibleViewArea.height()`, which in edge-to-edge mode doesn't correspond to the actual screen bottom. 3. **`KeyboardAvoidingView` used `_onKeyboardChange` for `keyboardDidHide` on Android**, which stores the event instead of nullifying it. After keyboard dismissal, any subsequent `onLayout` would re-enter `_updateBottomIfNecessary` with stale coordinates, causing a render loop (especially visible with `behavior="height"` in edge-to-edge mode, where `frame.y` shifts slightly on each layout cycle). This PR: - Removes `checkForKeyboardEventsLegacy` and uses `WindowInsetsCompat` APIs (via AndroidX) - Fixes `keyboardDidHide` to report `screenY` as `mVisibleViewArea.bottom + barInsets.bottom` - Fixes the Android `keyboardDidHide` listener in `KeyboardAvoidingView` to use `_onKeyboardHide`, matching the iOS behavior and breaking the render loop. Closes #49759 ## Changelog: [ANDROID] [FIXED] - Fix `KeyboardAvoidingView` on Android 15+ / with `edgeToEdgeEnabled` Pull Request resolved: #55855 Test Plan: Tested with `rn-tester` KeyboardAvoidingView example on: - Android 16 (API 36) (edge-to-edge is enforced) - Android 14 (API 34) with `edgeToEdgeEnabled` set to `true` or `false` - Android 7 (API 24) with `edgeToEdgeEnabled` set to `true` or `false` For each, verified: - `behavior="padding"`: keyboard open/close adjusts padding correctly - `behavior="position"`: keyboard open/close translates content correctly - `behavior="height"`: keyboard open/close works without render loop or glitching - `keyboardDidShow` and `keyboardDidHide` events report correct coordinates Reviewed By: mdvacca Differential Revision: D100437445 Pulled By: alanleedev fbshipit-source-id: 23b89a4380d02a0f524d587f89d023d21782406c
1 parent a8d44bc commit 4a6c933

File tree

2 files changed

+11
-73
lines changed

2 files changed

+11
-73
lines changed

packages/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ class KeyboardAvoidingView extends React.Component<
208208
];
209209
} else {
210210
this._subscriptions = [
211-
Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
211+
Keyboard.addListener('keyboardDidHide', this._onKeyboardHide),
212212
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
213213
];
214214
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

Lines changed: 10 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,25 @@
1717
import android.content.Context;
1818
import android.graphics.BlendMode;
1919
import android.graphics.Canvas;
20-
import android.graphics.Insets;
2120
import android.graphics.Paint;
2221
import android.graphics.Point;
2322
import android.graphics.Rect;
2423
import android.os.Build;
2524
import android.os.Bundle;
2625
import android.util.AttributeSet;
2726
import android.util.DisplayMetrics;
28-
import android.view.DisplayCutout;
2927
import android.view.KeyEvent;
3028
import android.view.MotionEvent;
3129
import android.view.Surface;
3230
import android.view.View;
3331
import android.view.ViewGroup;
3432
import android.view.ViewTreeObserver;
35-
import android.view.WindowInsets;
3633
import android.view.WindowManager;
3734
import android.widget.FrameLayout;
3835
import androidx.annotation.Nullable;
39-
import androidx.annotation.RequiresApi;
36+
import androidx.core.graphics.Insets;
37+
import androidx.core.view.ViewCompat;
38+
import androidx.core.view.WindowInsetsCompat;
4039
import com.facebook.common.logging.FLog;
4140
import com.facebook.infer.annotation.Assertions;
4241
import com.facebook.infer.annotation.ThreadConfined;
@@ -801,11 +800,7 @@ public void runApplication() {
801800

802801
@VisibleForTesting
803802
/* package */ void simulateCheckForKeyboardForTesting() {
804-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
805-
getCustomGlobalLayoutListener().checkForKeyboardEvents();
806-
} else {
807-
getCustomGlobalLayoutListener().checkForKeyboardEventsLegacy();
808-
}
803+
getCustomGlobalLayoutListener().checkForKeyboardEvents();
809804
}
810805

811806
private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
@@ -934,16 +929,13 @@ public boolean isViewAttachedToReactInstance() {
934929

935930
private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
936931
private final Rect mVisibleViewArea;
937-
private final int mMinKeyboardHeightDetected;
938932

939933
private boolean mKeyboardIsVisible = false;
940-
private int mKeyboardHeight = 0; // Only used in checkForKeyboardEventsLegacy path
941934
private int mDeviceRotation = 0;
942935

943936
/* package */ CustomGlobalLayoutListener() {
944937
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
945938
mVisibleViewArea = new Rect();
946-
mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
947939
}
948940

949941
@Override
@@ -952,31 +944,25 @@ public void onGlobalLayout() {
952944
return;
953945
}
954946

955-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
956-
checkForKeyboardEvents();
957-
} else {
958-
checkForKeyboardEventsLegacy();
959-
}
960-
947+
checkForKeyboardEvents();
961948
checkForDeviceOrientationChanges();
962949
checkForDeviceDimensionsChanges();
963950
}
964951

965-
@RequiresApi(api = Build.VERSION_CODES.R)
966952
private void checkForKeyboardEvents() {
967953
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
968-
WindowInsets rootInsets = getRootView().getRootWindowInsets();
954+
WindowInsetsCompat rootInsets = ViewCompat.getRootWindowInsets(getRootView());
969955
if (rootInsets == null) {
970956
return;
971957
}
972958

973-
boolean keyboardIsVisible = rootInsets.isVisible(WindowInsets.Type.ime());
959+
boolean keyboardIsVisible = rootInsets.isVisible(WindowInsetsCompat.Type.ime());
974960
if (keyboardIsVisible != mKeyboardIsVisible) {
975961
mKeyboardIsVisible = keyboardIsVisible;
962+
Insets barInsets = rootInsets.getInsets(WindowInsetsCompat.Type.systemBars());
976963

977964
if (keyboardIsVisible) {
978-
Insets imeInsets = rootInsets.getInsets(WindowInsets.Type.ime());
979-
Insets barInsets = rootInsets.getInsets(WindowInsets.Type.systemBars());
965+
Insets imeInsets = rootInsets.getInsets(WindowInsetsCompat.Type.ime());
980966
int height = imeInsets.bottom - barInsets.bottom;
981967

982968
ViewGroup.LayoutParams rootLayoutParams = getRootView().getLayoutParams();
@@ -999,62 +985,14 @@ private void checkForKeyboardEvents() {
999985
sendEvent(
1000986
"keyboardDidHide",
1001987
createKeyboardEventPayload(
1002-
PixelUtil.toDIPFromPixel(mVisibleViewArea.height()),
988+
PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom + barInsets.bottom),
1003989
0,
1004990
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
1005991
0));
1006992
}
1007993
}
1008994
}
1009995

1010-
private void checkForKeyboardEventsLegacy() {
1011-
getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
1012-
1013-
int notchHeight = 0;
1014-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
1015-
WindowInsets insets = getRootView().getRootWindowInsets();
1016-
if (insets != null) {
1017-
DisplayCutout displayCutout = insets.getDisplayCutout();
1018-
if (displayCutout != null) {
1019-
notchHeight = displayCutout.getSafeInsetTop();
1020-
}
1021-
}
1022-
}
1023-
final int heightDiff =
1024-
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels
1025-
- mVisibleViewArea.bottom
1026-
+ notchHeight;
1027-
1028-
boolean isKeyboardShowingOrKeyboardHeightChanged =
1029-
mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected;
1030-
1031-
if (isKeyboardShowingOrKeyboardHeightChanged) {
1032-
mKeyboardHeight = heightDiff;
1033-
mKeyboardIsVisible = true;
1034-
sendEvent(
1035-
"keyboardDidShow",
1036-
createKeyboardEventPayload(
1037-
PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom),
1038-
PixelUtil.toDIPFromPixel(mVisibleViewArea.left),
1039-
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
1040-
PixelUtil.toDIPFromPixel(mKeyboardHeight)));
1041-
return;
1042-
}
1043-
1044-
boolean isKeyboardHidden = mKeyboardHeight != 0 && heightDiff <= mMinKeyboardHeightDetected;
1045-
if (isKeyboardHidden) {
1046-
mKeyboardHeight = 0;
1047-
mKeyboardIsVisible = false;
1048-
sendEvent(
1049-
"keyboardDidHide",
1050-
createKeyboardEventPayload(
1051-
PixelUtil.toDIPFromPixel(mVisibleViewArea.height()),
1052-
0,
1053-
PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
1054-
0));
1055-
}
1056-
}
1057-
1058996
private void checkForDeviceOrientationChanges() {
1059997
final int rotation =
1060998
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))

0 commit comments

Comments
 (0)