Skip to content

feat(android): add LAST_KNOWN eventMode config option - #78

Open
lincolnthree wants to merge 7 commits into
ionic-team:mainfrom
lincolnthree:fix/android-keyboard-animation-heights
Open

feat(android): add LAST_KNOWN eventMode config option#78
lincolnthree wants to merge 7 commits into
ionic-team:mainfrom
lincolnthree:fix/android-keyboard-animation-heights

Conversation

@lincolnthree

@lincolnthree lincolnthree commented Aug 19, 2026

Copy link
Copy Markdown

Bug Report / Issue

Closes ionic-team/capacitor-plugins#2587

Description

On Android, the @capacitor/keyboard plugin can report inconsistent or incorrect keyboard heights (sometimes smaller than the actual keyboard, or 0) during keyboard animations or when WindowInsets are applied. The events keyboardWillShow and keyboardDidShow may fire with intermediate sizes instead of the final settled keyboard height, causing UI jumps or layout issues, particularly noticeable on newer Android versions (14/15) with edge-to-edge configurations.

This is a root cause of the common "Grey bar above keyboard" issue reported in many other issues (such as ionic-team/capacitor#8525, ionic-team/capacitor#8575, ionic-team/capacitor#8329, and ionic-team/capacitor-plugins#2205).

This PR introduces an eventMode: "LAST_KNOWN" configuration option for Android to smooth out the keyboard animation.

What it does:

  1. Tracks the Highest Known Height: Tracks the knownKeyboardHeight to avoid emitting intermediate sizes during the WindowInsetsAnimationCompat lifecycle (onPrepare, onProgress, onStart, onEnd).
  2. Smooths Event Emission: Only emits WILL_SHOW and DID_SHOW if the height actually changes, falling back to the LAST_KNOWN height instead of transient smaller heights during animation.

This change is backward compatible as the default eventMode remains "DEFAULT".

This adds an eventMode configuration option for Android to smooth out intermediate/jittering keyboard heights that are incorrectly emitted during WindowInsetsAnimation. Setting this to LAST_KNOWN ensures that listeners only receive the true/final keyboard height, resolving layout jump issues on Android 15/edge-to-edge configurations.
@kosserin

Copy link
Copy Markdown

Hey Lincoln, thanks for looking into this!

Your patch partially fixes the issue. It works correctly when the keyboard has no suggestion bar (or any other UI element rendered above the keyboard). However, with the Microsoft SwiftKey keyboard on a text input, the problem still occurs.

Steps to reproduce:

  1. Tap the input field
  2. Tap the background (to dismiss the keyboard)
  3. Quickly tap the input field again

The issue only appears in this specific sequence. Let me know if you need any more details or a screen recording.

@lincolnthree

Copy link
Copy Markdown
Author

Hey Lincoln, thanks for looking into this!

Your patch partially fixes the issue. It works correctly when the keyboard has no suggestion bar (or any other UI element rendered above the keyboard). However, with the Microsoft SwiftKey keyboard on a text input, the problem still occurs.

Steps to reproduce:

  1. Tap the input field
  2. Tap the background (to dismiss the keyboard)
  3. Quickly tap the input field again

The issue only appears in this specific sequence. Let me know if you need any more details or a screen recording.

Hey, thanks for trying the patch -- I have a few questions. It would be helpful to have some more information/context:

  1. You said it partially fixes the issue:
    1a) In what way was your issue fixed? Please describe in detail.
    1b) In what way was your issue NOT fixed? Please describe in detail.

  2. I don't have a Microsoft device. Could you please do some debugging and see where things are breaking down?

Attach the Android debugger -- Check the logs from: https://github.com/ionic-team/capacitor-keyboard/pull/78/changes#diff-bdfde9ceeea23c25067c2b23ad24db3f4ac5c312a7fed3e23c8f7451c6ea387fR125 and provide some specifics for the expected values and timings.

  1. Yes, a video recording would be very helpful.

Thank you!

@kosserin

Copy link
Copy Markdown

Hey,

it works correctly in some scenarios, but I'm still seeing incorrect height values in others. Here's what I found:

Issue 1: Incorrect height with Microsoft SwiftKey (initial show)

Both keyboardWillShow and keyboardDidShow return an incorrect height when the input type is email or text. Input types number and decimal work correctly.

Issue 2: Incorrect height on quick re-trigger (tap background → immediately refocus input)

keyboardWillShow: reproducible on SwiftKey and Gboard, with input types email and text
keyboardDidShow: reproducible on SwiftKey with input types email and text, and on Gboard with all input types

Screen recordings (demo app with a footer subscribed to keyboardHeight):

keyboardWillShow: https://jumpshare.com/share/LYPiwVVupIq2VTIeYF2l
keyboardDidShow: https://jumpshare.com/share/0ybIv8eIKnssB9Czad1V

Logs

keyboardWillShow — SwiftKey, input type text:
08-24 16:50:05.881 32753 32753 D Keyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=946 rawIme=946 nav=63 sdk=37
08-24 16:50:05.883 32753 32753 D Keyboard: onStart - showingKeyboard=true imeHeight=946 rawIme=946 nav=63 sdk=37
08-24 16:50:08.805 32753 32753 D Keyboard: onApplyWindowInsets - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 16:50:08.807 32753 32753 D Keyboard: onStart - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 16:50:09.111 32753 32753 D Keyboard: onApplyWindowInsets - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 16:50:09.182 32753 32753 D Keyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=714 rawIme=714 nav=63 sdk=37
08-24 16:50:09.184 32753 32753 D Keyboard: onStart - showingKeyboard=true imeHeight=714 rawIme=714 nav=63 sdk=37
08-24 16:50:09.485 32753 32753 D Keyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=946 rawIme=946 nav=63 sdk=37

keyboardDidShow logs (gboard text input type):
08-24 17:13:49.730 6484 6484 D CapKeyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=941 rawIme=941 nav=63 sdk=37
08-24 17:13:49.732 6484 6484 D CapKeyboard: onStart - showingKeyboard=true imeHeight=941 rawIme=941 nav=63 sdk=37
08-24 17:13:51.609 6484 6484 D CapKeyboard: onApplyWindowInsets - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 17:13:51.611 6484 6484 D CapKeyboard: onStart - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 17:13:51.908 6484 6484 D CapKeyboard: onApplyWindowInsets - showingKeyboard=false imeHeight=0 rawIme=0 nav=63 sdk=37
08-24 17:13:51.948 6484 6484 D CapKeyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=1038 rawIme=1038 nav=63 sdk=37
08-24 17:13:51.950 6484 6484 D CapKeyboard: onStart - showingKeyboard=true imeHeight=1038 rawIme=1038 nav=63 sdk=37
08-24 17:13:52.251 6484 6484 D CapKeyboard: onApplyWindowInsets - showingKeyboard=true imeHeight=941 rawIme=941 nav=63 sdk=37

Please let me know if you need anything else

@lincolnthree

Copy link
Copy Markdown
Author

@kosserin PR updated. please try again!

@kosserin

Copy link
Copy Markdown

I've tried the update and here are my observations:

  • On first tap or after couple of seconds tap on any input, only keyboardWillShow fires. keyboardDidShow never fires in that scenario. It fires in other cases. Reproducible on all keyboards.
  • On quick re-trigger after background tap, keyboardWillShow fires twice. Reproducible only on Microsoft SwiftKey keyboard for input type text and email. Gboard input focus after background tap now is working properly for all input types.
  • Switching between different input types doesn't fire keyboardWillShow nor keyboardDidShow. Reproducible only on Microsoft SwiftKey keyboard (keyboard that has different heights for different input types e.g.316 is text, 272 is decimal)
  • No keyboard event is being fired on keyboard height update (suggestion bar show/hide). Reproducible with Microsoft SwiftKey Keyboard on text and email input type modes.
  • Switching between keyboards (Gboard/Microsoft SwiftKey) doesn't trigger keyboardWillShow if keyboard height of that new keyboard is bigger than current.
  • On Android 13 there is a gap between keyboard and footer in value that is equal to navigation bar's height. Not sure if it's related to keyboard and if it should be solved by this plugin.

Attachments:
Screenshot_20260824_230641_ionic-tabs-app
Screenshot_20260824_230836_ionic-tabs-app

Screen recording of quick re-trigger with keyboardWillShow

screen-20260824-222515-1787603090421.mp4

Screen recording of switching between different input type modes

screen-20260824-230402-1787605429128.mp4

…tion glitches

* Extracts Android window inset math into a pure Java KeyboardHeightFilter state controller.
* Implements a Map<Integer, Integer> to track the maximum known keyboard height independently for different screen widths (e.g., Portrait vs Landscape).
* Fixes rapid-abort and SwiftKey double-fire glitches by capping spurious animation heights to the known ceiling.
* Fixes rotation transition bugs where the OS fires showing=true with a 0px height.
* Adds a comprehensive suite of pure JUnit tests to guarantee 100% coverage of all lifecycle and glitch scenarios.
* Retains android.util.Log traces in Keyboard.java to assist with production debugging.
@lincolnthree

lincolnthree commented Aug 26, 2026

Copy link
Copy Markdown
Author

@kosserin Thanks for all of this, the logs and scenarios were incredibly helpful! I believe almost all of these edge cases should now be fully accounted for.

I've just pushed a series of major updates to the patch:

Rotation & Split-Screen Support: I completely rewrote the ceiling logic to use a Map grouped by screen width. This allows the plugin to natively adapt its heights when you rotate between Portrait and Landscape (or resize the app), while still aggressively suppressing the rapid-abort and SwiftKey double-fire glitches.

The Android 13 Navigation Bar Gap: Interesting find on this! I did some digging and I don't think this is an OS bug, but an intended feature of Android 11+ WindowInsets. The OS should always return the absolute height from the bottom of the physical screen. However, if a Capacitor app isn't explicitly drawing Edge-to-Edge (resizeOnFullScreen = false), the webview stops above the Navigation Bar, meaning Capacitor was accidentally double-offsetting the web layout. I've added a fix that dynamically intercepts this and subtracts the Navigation Bar height, which should eliminate that empty gap.

If you have time to pull the latest changes, let me know how it goes!

…d height

* Fixes a bug in Capacitor where absolute Android WindowInsets were incorrectly passed to the WebView when resizeOnFullScreen is false.
* Adds calculateImeHeight to the KeyboardHeightFilter to translate absolute OS coordinates into relative WebView coordinates by subtracting the Navigation Bar height.
* Resolves the 40-100px double-offset empty gap above the keyboard in standard (non edge-to-edge) Capacitor apps.
* Adds testCalculateImeHeightAccountsForNavBar to guarantee 100% coverage of the math boundaries.
@kosserin

Copy link
Copy Markdown

Hey Lincoln, thank you for taking time to fix this and read my feedback.

I tested new changes and here's my feedback:

  • You fixed issue with navbar height on older androids (Android 13 in my case) but you messed up newer androids (see screenshot provided from my Android 16: https://ibb.co/4nVs6qMB)
  • All keyboards on switch between different inputs types works great
  • Gboard keyboard (keyboard with fixed height) works great in all scenarios
  • SwiftKey (keyboard with suggestion bar that seems to be on top of keyboard layout) triggers double keyboardWillShow events on quick retrigger after background tap (screen recording and logs provided down below)
  • keyboardDidShow for all keyboards trigger once and correct. Only issue is switching between keyboards (see next line)
  • Sometimes when switching keyboards (Gboard, SwiftKey, etc.) fires incorrect values both for keyboardWillShow and keyboardDidShow. Might be related to new keyboard height is bigger than last or vice-versa (screen recording and logs provided down below).
  1. SwiftKey double keyboardWillShow on quick retrigger (on start incorrect value and on end correct one)
screen-20260827-212310-1787858578822.mp4
  1. Switching keyboards sometimes fire incorrect values for both keyboardWillShow and keyboardDidShow
screen-20260827-215032-1787860207374.mp4

Logs for 1 (fresh tap on input field -> background tap -> quick tap on input field)
08-27 21:42:35.960 1622 1622 I Capacitor/Keyboard: onPrepareAnimation
08-27 21:42:35.963 1622 1622 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=316 emit=0 shouldEmit=false
08-27 21:42:35.964 1622 1622 I Capacitor/Keyboard: onStart: showing=true rawHeight=316 emit=316 shouldEmit=true
08-27 21:42:36.262 1622 1622 I Capacitor/Keyboard: onEnd: showing=true rawHeight=316 emit=316 shouldEmit=true
08-27 21:42:36.870 1622 1622 I Capacitor/Keyboard: onPrepareAnimation
08-27 21:42:36.878 1622 1622 I Capacitor/Keyboard: onApplyWindowInsets: showing=false rawHeight=0 emit=0 shouldEmit=false
08-27 21:42:36.878 1622 1622 I Capacitor/Keyboard: onStart: showing=false rawHeight=0 emit=0 shouldEmit=true
08-27 21:42:37.173 1622 1622 I Capacitor/Keyboard: onEnd: showing=false rawHeight=0 emit=0 shouldEmit=true
08-27 21:42:37.175 1622 1622 I Capacitor/Keyboard: onApplyWindowInsets: showing=false rawHeight=0 emit=0 shouldEmit=false
08-27 21:42:37.358 1622 1622 I Capacitor/Keyboard: onPrepareAnimation
08-27 21:42:37.375 1622 1622 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=272 emit=0 shouldEmit=false
08-27 21:42:37.376 1622 1622 I Capacitor/Keyboard: onStart: showing=true rawHeight=272 emit=272 shouldEmit=true
08-27 21:42:37.674 1622 1622 I Capacitor/Keyboard: onEnd: showing=true rawHeight=272 emit=272 shouldEmit=true
08-27 21:42:37.676 1622 1622 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=316 emit=316 shouldEmit=true

Logs for 2 (sometimes incorrect height fired on willShow and didShow on switching keyboards:
SwiftKey to Gboard not working properly:
08-27 21:52:57.076 3342 3342 I Capacitor/Keyboard: onApplyWindowInsets: showing=false rawHeight=0 emit=0 shouldEmit=false
08-27 21:52:57.285 3342 3342 I Capacitor/Keyboard: onPrepareAnimation
08-27 21:52:57.293 3342 3342 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=358 emit=0 shouldEmit=false
08-27 21:52:57.294 3342 3342 I Capacitor/Keyboard: onStart: showing=true rawHeight=358 emit=316 shouldEmit=true
08-27 21:52:57.595 3342 3342 I Capacitor/Keyboard: onEnd: showing=true rawHeight=358 emit=316 shouldEmit=true

Android to Gboard not working properly:
08-27 21:56:20.093 3342 3342 I Capacitor/Keyboard: onPrepareAnimation
08-27 21:56:20.104 3342 3342 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=358 emit=0 shouldEmit=false
08-27 21:56:20.105 3342 3342 I Capacitor/Keyboard: onStart: showing=true rawHeight=358 emit=343 shouldEmit=true
08-27 21:56:20.407 3342 3342 I Capacitor/Keyboard: onEnd: showing=true rawHeight=358 emit=343 shouldEmit=true

SwiftKey to Gboard working good:
08-27 22:04:24.679 3342 3342 I Capacitor/Keyboard: onApplyWindowInsets: showing=false rawHeight=0 emit=0 shouldEmit=false
08-27 22:04:24.894 3342 3342 I Capacitor/Keyboard: onPrepareAnimation
08-27 22:04:24.904 3342 3342 I Capacitor/Keyboard: onApplyWindowInsets: showing=true rawHeight=316 emit=0 shouldEmit=false
08-27 22:04:24.905 3342 3342 I Capacitor/Keyboard: onStart: showing=true rawHeight=316 emit=316 shouldEmit=true
08-27 22:04:25.207 3342 3342 I Capacitor/Keyboard: onEnd: showing=true rawHeight=316 emit=316 shouldEmit=true

Please let me know if you need anything else. Happy to help get this solved!

When an OS rotation layout pass reports an invalid layout height that triggers the known-height clamp, it produces a transient event that will be corrected by another layout pass a few milliseconds later. This commit ensures we do not emit 'keyboardDidShow' to the Javascript layer for these transient glitches, preventing unnecessary DOM recalculations mid-rotation.
Automatically disables nav bar subtraction on Android 15+ devices where Edge-to-Edge is enforced by the OS. Exposes a new 'navigationBarInsets' config option to allow developers to manually override this behavior.
@lincolnthree

Copy link
Copy Markdown
Author

@kosserin Your continued testing is very helpful :) To be honest I don't know that we will get this perfect. Unfortunately with the OS sending incorrect values at various times, there is only so much we can do to correct this. Everything is a trade-off since this isn't "acting normally" at the Android level.

Try the latest version and let me know if the remaining issues are resolved?

Note the new configuration settings for navigationBarInsets: https://github.com/ionic-team/capacitor-keyboard/pull/78/changes#diff-f1f3c6677661c21128a30f64358c6615c6801043e1cbfcc904302422a17b3246R66

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Inconsistent Keyboard Height on Android during WindowInsetsAnimation (Proposed Patch)

2 participants