Android keyboard performance patch#155
Open
PovilasKorop wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hey guys,
First, warning - it's a vibe-coded patch with Opus, as I'm not a mobile/Java developer, so please take it either "as is", or some parts of it, or just as an idea. But it actually solved a real issue reported by real audience.
When I launched my app Checklisty, some people reported slow keyboard movement on Android.
I didn't reproduced it at first, but then tried on older phones, and the slowness became apparent on Samsung Galaxy S20+ and Galaxy A55. It didn't appear on the newer Samsung Galaxy S23.
Here's the video of the slowness:
NativePHP-Keyboard-Slowness_timebolted.mp4
Or link to Google Drive if the file upload above doesn't work:
https://drive.google.com/file/d/1b-dFfkWce9I_R43-wBwFRPvOnZbfGrR1/view?usp=drive_link
And here are the comments from Claude Code related to this PR.
As I mentioned I'm not a pro here, but I thought I would still contribute THIS after the investigation and Claude Code session than just reporting the issue.
Hope that helps!
Android Keyboard Performance Patch (NativePHP Mobile)
Fixes the slow / clunky / glitchy on-screen keyboard animation reported on Android.
All changes live in one file:
Background: what the recording showed
Frame-by-frame analysis of a screen recording (full frame rate) measured:
evaluateJavascripteach time.So two root causes: (a) per-frame main-thread work during the animation, and (b) a white gap caused by the bottom nav vacating its space before the keyboard covered it.
Change 1 — Skip redundant safe-area JS injection on every keyboard frame
Why:
setOnApplyWindowInsetsListenerfires on every frame of the keyboard (IME) animation, because the IME inset changes each frame. The listener unconditionally calledinjectSafeAreaInsets(...), which runs a ~70-lineevaluateJavascript(removes a<style>, recreates it, writes 4 CSS variables, adds a class) against the WebView. That meant ~60 heavy JS injections per single keyboard open/close, clogging the main thread and making the keyboard feel slow/janky. The system bars do not change while the keyboard animates, so this work is entirely redundant. We guard it so it only runs when the system-bar insets actually change.1a. New field — after template line 67
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.kt1b. Guard the injection — template lines 94–97
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.ktChange 2 — Resize the WebView once, not every frame
Why:
WindowInsets.imeis the animated inset value — it changes every frame, so the WebView's bottom padding (and therefore its size) was re-applied on every frame, forcing a full web-page reflow per frame.WindowInsets.imeAnimationTargetis the final keyboard height, so the WebView resizes once when the keyboard toggles while the system still animates the keyboard sliding over the content. This removes ~60 per-frame WebView reflows.2a. Use the animation target — template line 890
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.kt2b. Opt in to the experimental API — before
MainScreen()at template line 829imeAnimationTargetis annotated@ExperimentalLayoutApi, so the composable that uses it must opt in or the build fails with "The API of this layout is experimental…".File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.kt+ @OptIn(ExperimentalLayoutApi::class) @Composable private fun MainScreen() {Change 3 — Don't flash white during the keyboard/reflow gap
Why: During the brief gap (and any WebView reflow), the WebView's default white background was showing as a bright flash. Making the WebView transparent and putting a theme-matched solid color behind it means the gap shows a matching color instead of a jarring white flash — a big improvement in dark mode especially.
3a. Transparent WebView — in the factory, after template line 114
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.kt3b. Themed backdrop behind the WebView — template line 830
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.ktprivate fun MainScreen() { - Box(Modifier.fillMaxSize()) { + // Solid themed backdrop behind the (transparent) WebView so any keyboard/ + // reflow gap shows a matching color instead of a bright white flash. + Box( + Modifier + .fillMaxSize() + .background(if (isSystemInDarkTheme()) Color.Black else Color.White) + ) {Change 4 — Keep the bottom nav in place during keyboard transitions
Why: This is the direct cause of the white gap the recording revealed. The bottom nav slid out (150 ms) the instant the keyboard became visible, but the keyboard takes ~185 ms to appear — so for ~40–80 ms there was an empty region (nav already gone, keyboard not yet there). Keeping the nav visible lets the keyboard slide up over it as one continuous motion, eliminating the gap.
4a. Remove the now-unused state read — template line 964
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.ktprivate fun BottomNavigationContent() { - val isKeyboardVisible by NativeUIState.isKeyboardVisible val bottomNavData by NativeUIState.bottomNavData4b. Always show the nav — template lines 971–973
File:
resources/androidstudio/app/src/main/java/com/nativephp/mobile/ui/MainActivity.ktSummary
injectSafeAreaInsetswithlastSystemBarsimeAnimationTarget+@OptInNot addressed (out of scope): the first-show input latency (IME / WebView cold-start before anything happens) — that is system warm-up, not in this code path.