Skip to content

android: Hide the keyboard on a tap in a scroll view when the field is outside it - #717

Merged
huacnlee merged 4 commits into
longbridge:mainfrom
jt-wang:android-tap-hides-keyboard
Oct 31, 2026
Merged

huacnlee merged 4 commits into
longbridge:mainfrom
jt-wang:android-tap-hides-keyboard

Conversation

@jt-wang

@jt-wang jt-wang commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Description

On Android, a tap in a scroll view does nothing while the keyboard is up, when the focused field sits outside the scroll view. That is the layout of every chat screen: the messages scroll, the composer sits below them. With the default keyboardShouldPersistTaps="never" the tap is taken to dismiss the keyboard (""never": the tap only dismisses it", website/modules/nsh-list.md), yet the keyboard stays up and the row under the finger does not hear the press either.

The cause is ScrollBox.hideKeyboard(): it looks for the focused view with findFocus(), which searches only the scroll view's own subtree. The composer is not in it, so nothing is hidden.

The fix looks up focus from the root view, and falls back to Mounter.focusedInput(). The change is 3 lines in ScrollBox.java, plus a regression test (examples/gallery/tests/keyboard-taps.test.tsx and its fixture). No API change, no new dependency.

How to Test

Before and after on an Android 15 emulator

The fixture has a scroll view with one row, a button that counts its presses, and below it, outside the scroll view, an Input; a line shows keyboardFrame(). The test types into the field, presses the row and waits for "keyboard: down":

cd examples/gallery
nsh test tests/keyboard-taps.test.tsx --target android   # an Android device or emulator
nsh test tests/keyboard-taps.test.tsx                    # headless
On dd70df2 (main at 201d780 merged in) --target android (Android 15 arm64 emulator) headless
Without the fix fails: `text:keyboard: down` showing never happened in 5000 ms; on screen: ["keyboard: up", "Row 1, pressed 0"] passes
With the fix passes passes

With the fix the GIF also shows the row's onPress running ("pressed 1"). The docs above say a "never" tap only dismisses the keyboard; this PR does not change which view hears the tap, and the test does not check it. Tell me if you want that tap kept from the row as well.

CI runs no --target android job, so the Android row is a command for the reviewer to run. The runtime was built with cargo xtask android --release --dev --abi arm64-v8a.

Not tested: iOS, which takes a separate path; a physical device; armeabi-v7a and x86_64.

Checklist

  • I have read the CONTRIBUTING document and followed the guidelines.
  • Reviewed the changes in this PR and confirmed AI generated code (if any) is accurate.
  • Passed cargo test --workspace, cargo +nightly fmt --all and clippy (including the Catalyst and Android runs for platform code). (cargo test --workspace on dde7f77 with this commit and the four other Android fixes of this series: 3260 passed, 0 failed, 43 ignored. cargo xtask clippy-android passes on main at 201d780, and this PR changes no Rust. The Java is compiled by cargo xtask android, and there is no Rust for fmt to check)
  • Changes to what apps use keep what released versions shipped and come with a case in tests/compat/ (cargo xtask compat). (No API change.)
  • Ran the related examples/gallery demos on iOS and Android (if the change is visible or platform-specific). (The test above, on Android.)

…s outside it

keyboardShouldPersistTaps "never" swallows the tap to hide the keyboard,
but looked for the focused field only inside the scroll view. A chat's
composer sits below its list, so the tap did nothing at all: the keyboard
stayed up and the row under the finger never got the press.
@huacnlee
huacnlee merged commit 7e846de into longbridge:main Oct 31, 2026
12 checks passed
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.

2 participants