Repository navigation
feat(android): add LAST_KNOWN eventMode config option - #78
lincolnthree wants to merge 8 commits into
Conversation
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.
|
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:
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:
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.
Thank you! |
|
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 Screen recordings (demo app with a footer subscribed to keyboardHeight): keyboardWillShow: https://jumpshare.com/share/LYPiwVVupIq2VTIeYF2l Logs keyboardWillShow — SwiftKey, input type text: keyboardDidShow logs (gboard text input type): Please let me know if you need anything else |
…om corrupting keyboard ceiling
|
@kosserin PR updated. please try again! |
|
I've tried the update and here are my observations:
Screen recording of quick re-trigger with keyboardWillShow screen-20260824-222515-1787603090421.mp4Screen 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.
|
@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.
|
Hey Lincoln, thank you for taking time to fix this and read my feedback. I tested new changes and here's my feedback:
screen-20260827-212310-1787858578822.mp4
screen-20260827-215032-1787860207374.mp4Logs for 1 (fresh tap on input field -> background tap -> quick tap on input field) Logs for 2 (sometimes incorrect height fired on willShow and didShow on switching keyboards: Android to Gboard not working properly: SwiftKey to Gboard working good: 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.
|
@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 |
|
@kosserin Have you had a chance to test the latest? I am interested if your remaining issues are fixed. |
|
Hey Lincoln, sorry for taking some time to respond. I was quite busy with some other tasks that were not related to Capacitor.
screen-20260901-192558-1788283544057.mp4.2.mp4Logs: Switching between different height (different input types) triggers willShow update only once. After it, no events being triggered. screen-20260901-192814-1788283685056.mp4Logs: Focusing input with bigger keyboard layout and closing (no matter if quick or not) and then focusing input with smaller keyboard layout causes update to not trigger. screen-20260901-192959-1788283791565.mp4Logs: Focusing input with smaller keyboard layout and then focusing (with delay of 1-2s) input with bigger keyboard layout causes no willShow event to be fired. screen-20260901-193320-1788283992391.mp4Logs: Focusing input with smaller keyboard layout (e.g input type number) and closing keyboard and quickly press on bigger keyboard layout (e.g. input type text) it fires willShow two times and at the end updates correctly but still not smooth |
|
With the help of AI and your current solution, I managed to get it almost perfect. On this repo on keyboard-input-context branch I patched your solution and it is almost working great. It is working in all cases except when I 3+ times really quickly re-trigger input, then footer jumps more than it should (keyboardWillShow fires bigger height) and then resizes accordingly). Sorry for not providing you with forked version of your branch, I don't have time atm to do so and I wanted to inform you about what I managed to get. Thank you for your time and looking forward on your feedback! |
|
Thank you! No worries. This is a really tricky problem. It's just the one commit? kosserin/android-capacitor-keyboard-issue@1f3a416 Taking a look now.
|
|
Wow, ok. So it looks like you went a step farther and provided JS/web code to assist in identifying the input type? |
|
I thought about that but was hesitant to do things on the JS side due to developer burden/inconsistency, but using |
…IMEs (e.g. SwiftKey) Includes automated focusin interception in JS bridge to relay input context to native. The JS listener is hardened to strictly penetrate ShadowDOM and deduplicate bridge traffic.
|
@kosserin This is really good. Thank you (and Claude). I've adapted the parts of your patch to the PR and made a few improvements: The focusin listener is now built directly into the plugin's JS wrapper (src/index.ts), so a custom Angular keyboard.service.ts is no longer required. (It also uses composedPath to support ShadowDOM elements like Ionic inputs). Try out the new patch and you should be able to delete your keyboard service now! We're getting there! |
|
PS. Really good idea to use the runnable to query the OS for the height as a sanity check. I love that. |
|
Hey Lincoln, I just checked quickly your changes. Everything works great! This is the smoothest one so far! Didn't find any issue yet. I will play around with it and write back to you in a day or max two whether I've found some issues and I hope and believe it stays that. |
|
YES. That's what I like to hear. Thank you for finding the missing piece of the puzzle. |
|
Hey Lincoln, The only issue I have found is jump of footer by the value of ion-safe-area-bottom on keyboardWillHide if ion-footer contains ion-safe-area-bottom:
I hope screen recording will help you get my point: With padding-bottom: var(--ion-safe-area-bottom) on ion-toolbar inside ion-footer: screen-20260905-125236-1788605548311.mp4Wit padding-bottom: 0 on ion-toolbar inside ion-footer (no glitch jump): screen-20260905-125432-1788605662781.mp4I'm not sure if this is related to this, as this might be a separate issue. Your current solution solved all issues I had with incorrect/late keyboard height on keyboardWillShow so I'm totally satisfied with this solution. |
|
AI Summary of the issue: The jump is caused by how the underlying Chromium WebView handles CSS Environment variables during native animations.
I think you have a custom transition on your footer, right? The way I solved this was to not transition the element as the keyboard closes. The transition is on keyboardWillOpen only -- when the keyboard closes, the elements instantly drop back down to the bottom of the viewport. The other reason is the CSS animations often lag slightly behind the keyboard closing and it can look a little weird. |
|
@lincolnthree I would like to buy you a coffee for the help. You did a great job. Do you have a sponsor link by any chance? |
Aw thanks, I appreciate that but no I don't. You helped get this over the line just as much as I did! Thanks to you we figured out the full puzzle. You can check out my app though :) www.topdecked.com -- what are you building? |
|
Hey Lincoln, I'm building a mobile hybrid payment app using Angular, Ionic and Capacitor. Currently learning Next.js, BE and building fullstack projects in my spare time :) |
|
Hey @lincolnthree , I tested it once again and I can confirm it is working properly, but didn't have time to push it to main branch as it's unfortunately not top priority at this moment. I believe in next week or so I will push it forward so others can report if something has to be addressed |


Bug Report / Issue
Closes #80
Description
On Android, the
@capacitor/keyboardplugin can report inconsistent or incorrect keyboard heights (sometimes smaller than the actual keyboard, or0) during keyboard animations or whenWindowInsetsare applied. The eventskeyboardWillShowandkeyboardDidShowmay 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:
knownKeyboardHeightto avoid emitting intermediate sizes during theWindowInsetsAnimationCompatlifecycle (onPrepare,onProgress,onStart,onEnd).WILL_SHOWandDID_SHOWif the height actually changes, falling back to theLAST_KNOWNheight instead of transient smaller heights during animation.This change is backward compatible as the default
eventModeremains"DEFAULT".