Keyboard Handling — Android Validation Handoff¶
Self-contained brief for validating the react-native-keyboard-controller keyboard
work on Android. The migration was implemented and hand-verified on iOS (iPad +
simulator). Android automated validation was deferred to a Linux machine with a KVM
emulator; this page is what an agent (or engineer) reads to run it cold.
What was changed (the one pattern)¶
Keyboard avoidance is standardized on react-native-keyboard-controller:
KeyboardProviderwraps the app once infrontend/app/_layout.tsx(insideSafeAreaProvider, aboveThemeProvider; an ancestor ofTrueSheetProvider).frontend/hooks/useKeyboard.tsexposesuseKeyboard()(Reanimatedheight/progress+visible);useKeyboardVisible()re-exports from it.frontend/components/ui/KeyboardAwareScroll.tsxis the one scroll container for normal (non-sheet) screens. Web falls back to a plainAnimated.ScrollView.- Docked bars that must ride the keyboard use the library's
KeyboardStickyView(chat/QuickReplyBar.tsx,app/(capture)/compose-moment.tsxpublish footer). - Sheets keep TrueSheet's native keyboard handling. No keyboard-controller view
is nested inside a
TrueSheet.components/ui/Sheet.tsxnow defaultsfooterOptions.keyboardOffsetso docked sheet footers clear the keyboard.
The Android-specific reason this matters: Expo SDK 55 forces edge-to-edge, under which
the IME arrives as a window inset. The library consumes that inset (WindowInsetsCompat),
which is what the manifest adjustResize alone no longer does. Confirming that on a real
Android surface is the point of this pass.
Environment (Linux emulator)¶
Follow the emulator setup in Native Testing ("Quick Android emulator")
and the android-emulator-qa skill. Key points:
- Backend host: the emulator's
localhostis itself. Point the app at10.0.2.2:8080(emulator alias for the host loopback) or the Mac/host LAN IP.EXPO_PUBLIC_API_URLis inlined at bundle time and.env.localbeats a shell export, so edit.env.localthenexpo start --clear. - Credentials: env only, never hardcoded.
TOMODA_E2E_USER/TOMODA_E2E_PASS. Local seed logins aretestuserN@tomoda.life/password; the adb login flow prefers a username without special characters (e.g.tanakatesto), since soft-keyboard text injection drops@/.. - Driver:
frontend/scripts/android-e2e.sh(adb:tap_id/wait_id/open_sheetby uiautomator resource-id = testID;input text;screencappulled to.android-e2e/). The "vision" check is the agent reading each PNG.
The keyboard flow to add to android-e2e.sh¶
Add a keyboard case to the case block (run after login). Each row: focus the
field, type, capture a PNG, then read the PNG and assert the condition.
| Flow | Open via | testID | Assert on the screenshot | Fixes |
|---|---|---|---|---|
| Chat composer | open a chat | chat-message-input |
composer + send visible above keyboard | iOS-cover / Android inset |
| compose-moment | capture flow → compose | compose-publish |
caption visible; Publish riding the keyboard | caption covered; footer covered |
| QuickReplyBar | open a moment → reply | quick-reply-input |
reply bar sits ~8px above the keyboard, not a keyboard-height too high | Android double-lift |
| Friends search | friends → search, tap first result | search field + first row | first tap selects the row (navigates), not just dismiss | first-tap swallowed |
| MomentEditSheet | navigate to a moment → tap edit (see note) | sheet root + -footer |
caption scrolls clear; docked Save above keyboard | non-scroll form stranded inputs |
| Credential form | change-password route | change-password-new |
field + submit above keyboard | inconsistent Android behavior |
testID / open-path prerequisites¶
Instrumented in code already: chat-message-input, quick-reply-input,
compose-publish, change-email-input, change-username-input,
change-password-new, change-password-confirm.
Sheets that a headless adb tap cannot present (native BottomSheetDialog) open via the
__DEV__ useE2ESheet deep link (frontend/utils/e2eSheet.ts), registered for
quickadd, create-plan, create-event: tomoda://<route>?__sheet=<name>.
MomentEditSheet is a per-card sheet (components/moments/MomentPostcard.tsx), so it is
not deep-link registered. Reach it by navigating to a moment and tapping its edit
affordance by testID rather than a __sheet= link.
Expected results¶
Each row above should pass, matching the iOS behavior. Classify a failure before filing:
- CODE: the keyboard covers the field / the bar sits at the wrong height / the first tap is swallowed. File against the surface name.
- NETWORK / env: login fails, no data, blank lists. Fix the backend host / seed /
.env.localand re-bundle; not a keyboard bug.
The single most load-bearing Android check is that focusing any migrated input under edge-to-edge actually shifts content up (the library consuming the IME inset). If that holds, the pattern is proven on Android.
See also¶
- Native Testing — the emulator setup and the broader focus/keyboard checklist
android-emulator-qaskill — the authoritative adb driver writeup