Skip to content

Split - Input is not focused when keyboard is opened after switching to other tab #101157

Open

Description

@applause-bot

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: 9.4.78-0
Reproducible in staging?: Yes
Reproducible in production?: No
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #100149
Email or phone of affected tester (no customers): jsdoijnodsdisojo@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Samsung Galaxy S25 FE / Android 16
App Component: Money Requests

Action Performed:

  1. Launch Expensify app.
  2. Go to workspace chat.
  3. Create an expense.
  4. Open the expense.
  5. Tap More > Split > Percent.
  6. Tap on the input field.
  7. Switch to Amount tab.

Expected Result:

When keyboard is opened on Amount tab, the input field should be focused.

Actual Result:

When keyboard is opened on Amount tab, the input field is not focused.
Entering amount changes the amount split field without focus.

Workaround:

Unknown

Platforms:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome Safari

Screenshots/Videos

Bug7259910_1789436939103.Screen_Recording_20260915_094633_Expensify.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Julia Ponikarchuk
Bug ID: 7259910

Issue OwnerCurrent Issue Owner: @TaduJR

Activity

  1. added
    DeployBlockerCashThis issue or pull request should block deployment
    BugSomething is broken. Auto assigns a BugZero manager.
    on Sep 15, 2026
  2. applause-bot commented on Sep 15, 2026

    @applause-bot
    Author

    While Applause doesn't embed attachments for this bug, here are the links for quick view:
    https://platform.applause.com/services/links/v1/external/3866294c2dd6dd971b489a05a4c1421cdca437c61c5fae2207317f6384ec831e

  3. melvin-bot commented on Sep 15, 2026

    @melvin-bot

    You have been assigned to this deploy blocker because you recently merged this PR: #100149

  4. melvin-bot commented on Sep 15, 2026

    @melvin-bot

    @carlosmiceli @brunovjk you have been assigned to this deploy blocker because you reviewed the offending PR: #100149

  5. github-actions commented on Sep 15, 2026

    @github-actions
    Contributor

    👋 Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open `StagingDeployCash` deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

    1. Identify the pull request that introduced this issue and revert it.
    2. Find someone who can quickly fix the issue.
    3. Fix the issue yourself.
  6. melvin-bot commented on Sep 15, 2026

    @melvin-bot

    @TaduJR you have been assigned to this deploy blocker because you authored the offending PR: #100149

  7. melvin-bot commented on Sep 15, 2026

    @melvin-bot

    💬 A slack conversation has been started in #expensify-open-source

  8. MelvinBot commented on Sep 15, 2026

    @MelvinBot
    Contributor

    🔍 Investigation Summary

    Classification: Frontend bug
    Causing PR: #100149 - "fix: Android - Split - Percentage input has to be focused twice to be able to edit it" by @TaduJR (High confidence)
    Related Issues: #78089 (the bug #100149 was fixing — a revert reintroduces it)

    Recommendation: ROLL FORWARD

    #100149 bumps react-native-pager-view 8.0.0 → 9.0.4, and v9.0.0 replaced the entire Android pager with a Jetpack Compose HorizontalPager, which no longer preserves Android view focus across a page change. Reverting is clean but reintroduces #78089 and swaps the native pager under all 7 OnyxTabNavigator call sites on deploy-blocker timing, so the one-prop App-side fix below is the lower-risk path.

    Assigned: @TaduJR (PR author), @brunovjk and @carlosmiceli (approving reviewers) from #100149 — already assigned by melvin-bot, and this investigation confirms that assignment was correct (see caveat below).
    Labels: No changes. DeployBlockerCash stays (an App PR caused this, and it does not reproduce on production). DeployBlocker was not present, so nothing to remove.

    📋 Detailed Analysis

    Evidence

    The auto-assignment came from the tester pasting the PR link into the "caught during regression testing" field, so it was not independent evidence. I verified it separately, and it holds up.

    1. The entire Split-expense focus code path is byte-identical between production and staging.

    git diff origin/production origin/staging (27adede6d33 → 5b9770e2155, i.e. 9.4.77-4 → 9.4.78-0) across every file that can affect this flow — src/pages/iou/SplitList/, SplitListItem.tsx, SplitListItem/, useAutoFocusInput.ts, DynamicSplitExpensePage.tsx, OnyxTabNavigator.tsx, OnyxTabNavigatorConfig/, TabSelector/, PercentageForm, MoneyRequestAmountInput, ScreenFocusArbiter.ts, NavigationFocusReturn.ts — returns only:

     package.json                                   |  4 ++--
     src/components/TabSelector/TabSelectorBase.tsx |  1 +
     src/components/TabSelector/TabSelectorItem.tsx | 20 +++++++++++++-------
     src/components/TabSelector/types.ts            | 12 ++++++++++++
    

    The TabSelector delta only adds an EducationalTooltip around a tab badge, and it renders solely when badgeText is set — the Split tabs have no badges, so it is inert here. The package.json delta is the version string plus react-native-pager-view: 8.0.0 → 9.0.4.

    git log over the Split focus files confirms the same thing from the other direction: nothing has landed in src/pages/iou/SplitList/, SplitListItem.tsx, SplitListItem/, useAutoFocusInput.ts or DynamicSplitExpensePage.tsx since 2026-09-10, before this deploy window opened.

    2. No other PR in the checklist is a candidate. I scanned all 38 PRs in #101132. #100149 is the only one touching anything in this code path, and it is the only native/dependency change affecting the pager.

    So the only behavioural delta that can explain an Android-only, staging-only tab-focus regression is the react-native-pager-view major bump.

    3. The bump is a full Android rewrite, not a patch. Upstream release notes for v9.0.0 read feat(android): jetpack compose support (#1092). v8 shipped ViewPagerAdapter.kt / ViewPagerViewHolder.kt / NestedScrollableHost.kt backed by androidx.viewpager2.widget.ViewPager2; v9 ships ComposePagerView.kt backed by androidx.compose.foundation.pager.HorizontalPager.

    Reproduces on staging only, not production — consistent with the bump being the sole delta.

    Verification

    react-native-tab-view (used by @react-navigation/material-top-tabs, which OnyxTabNavigator builds on) renders through react-native-pager-view — confirmed in node_modules/react-native-tab-view/src/PagerViewAdapter.tsx. So every OnyxTabNavigator, including the Split tabs, goes through the swapped native implementation.

    Affected App files (all permalinked at the deployed staging SHA 5b9770e2155):

    Library-side confirmation in the installed v9.0.4:

    • node_modules/react-native-pager-view/android/src/main/java/com/reactnativepagerview/ComposePagerView.kt:584-610 — each RN page is hosted in an AndroidView, and attachChild re-parents the page view ((child.parent as? ViewGroup)?.removeView(child) → container.removeAllViews() → container.addView(child, …)).
    • Same file, :646-656 — DEFAULT_BEYOND_VIEWPORT_PAGE_COUNT = 1, so a page two positions away leaves the composition and its AndroidView is disposed.
    • grep -rn -i "focus\|descendant" node_modules/react-native-pager-view/android/src/main/ returns zero matches: the v9 Android implementation never requests focus for the incoming page, nor manages focus containment.

    Root Cause

    1. On the Percent tab the user taps a percentage input. The native EditText takes Android focus and the soft keyboard opens.
    2. The user taps the Amount tab. Because DynamicSplitExpensePage does not pass shouldDismissKeyboardBeforeTabSwitch, handleTabPress returns early (OnyxTabNavigator.tsx:299-302) and the jump happens immediately, with keyboardDismissMode="none" keeping the keyboard up.
    3. Under v8, pages lived in a ViewPager2/RecyclerView ViewGroup with ordinary Android view-focus semantics and stayed attached within offscreenPageLimit. Under v9, the page change runs through Compose: attachChild detaches and re-parents the page views, and ViewGroup.removeView on a subtree that holds focus makes Android clear that focus.
    4. Nothing then re-establishes focus on the Amount tab — not the library (no focus code at all in v9's Android sources) and not JS (the auto-focus ref is only attached for a selected row, and its focus attempt is gated).

    The result is exactly the reported state: the keyboard is up, but no input is visibly focused, so onFocus → focusHandler → onInputFocus never fires and the JS-side focused styling (hideFocusedState={false}) and scroll-into-view never run, even though keystrokes still land in the amount field.

    Flagged as unverified: I could not run an Android build here, so the precise native detail of which view ends up holding the input connection after the re-parent (step 4) is Medium confidence. The causation — that the react-native-pager-view bump in #100149 is the only possible source of this regression — is High confidence, since it is the sole delta in this code path between production and staging.

    Suggested fix (roll forward)

    One prop, in src/pages/iou/DynamicSplitExpensePage.tsx:640:

    <OnyxTabNavigator
        id={CONST.TAB.SPLIT_EXPENSE_TAB_TYPE}
        defaultSelectedTab={CONST.TAB.SPLIT.AMOUNT}
        tabBar={TabSelector}
        shouldDismissKeyboardBeforeTabSwitch
    >

    This prop exists for exactly this case — see its doc comment at OnyxTabNavigator.tsx#L73-L78 ("Opt in from tab screens that hold a text input next to tabs…") — it already ships in production, and DistanceRequestStartPage.tsx#L181 is the existing precedent. With it, the keyboard is fully dismissed before the jump, so the user never lands on the Amount tab with a keyboard up over an unfocused input; tapping the amount input then focuses it normally.

    Fallback: if that cannot be validated on an Android device inside the deploy window, revert #100149 instead. It is a clean 3-file revert (package.json, package-lock.json, ios/Podfile.lock), but note it reintroduces #78089 and has a companion Mobile-Expensify PR linked from #100149 that needs to be handled alongside it.

    💡 Suggested AI code review rule

    This bug looks like a recurring, mechanically detectable pattern, so it may be worth a coding-standards rule so the AI reviewer catches the next one. Proposed for Expensify/App:

    • Rule: UI-4 Dismiss the keyboard before switching tabs when a tab screen holds a text input
    • Condition: Flag an OnyxTabNavigator whose tab screens render a text input (TextInput, MoneyRequestAmountInput, PercentageForm, AmountForm) but that does not pass shouldDismissKeyboardBeforeTabSwitch; add the prop so the tab only changes once the keyboard is fully hidden.
    • Why: With keyboardDismissMode="none", a synchronous tab jump leaves the soft keyboard up over the outgoing tab's focused input. Whether focus survives is then up to the native pager's view-recycling semantics, which is why a react-native-pager-view major bump (fix: Android - Split - Percentage input has to be focused twice to be able to edit it #100149) turned this latent gap into a deploy blocker (this issue). Currently 6 of the 7 OnyxTabNavigator call sites omit the prop, including IOURequestStartPage.tsx#L298 whose Manual tab holds the amount input.
    • Search pattern: files matching grep -rl "OnyxTabNavigator" src/pages that do not also match shouldDismissKeyboardBeforeTabSwitch, then check whether any TopTab.Screen subtree renders a text input.
    • Offending code (❌): DynamicSplitExpensePage.tsx#L640-L644
    • Fix (✅): DistanceRequestStartPage.tsx#L176-L182

    The full rule file, with the ❌/✅ code inline in App's coding-standards format, would go in Expensify/App itself (.claude/skills/app-coding-standards/rules/ui-4-*.md plus a SKILL.md index bullet). If it's worth adding, a maintainer can open it there as a draft PR.

  9. 22 remaining items

  10. melvin-bot commented on Oct 5, 2026

    @melvin-bot

    @TaduJR Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  11. melvin-bot commented on Oct 7, 2026

    @melvin-bot

    @TaduJR Eep! 4 days overdue now. Issues have feelings too...

  12. TaduJR commented on Oct 9, 2026

    @TaduJR
    Contributor

    Not Overdue. Upstream merged.

    Waiting for release.

  13. melvin-bot commented on Oct 12, 2026

    @melvin-bot

    @TaduJR Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

  14. KisaneNeko commented on Oct 13, 2026

    @KisaneNeko

    Fix is now available in RN Pager View v9.0.8

  15. melvin-bot commented on Oct 13, 2026

    @melvin-bot

    📣 @KisaneNeko! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  16. melvin-bot commented on Oct 14, 2026

    @melvin-bot

    @TaduJR Huh... This is 4 days overdue. Who can take care of this?

  17. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    @TaduJR Still overdue 6 days?! Let's take care of this!

  18. TaduJR commented on Oct 20, 2026

    @TaduJR
    Contributor

    Not Overdue

  19. carlosmiceli commented on Oct 21, 2026

    @carlosmiceli
    Contributor

    @TaduJR what's the hold at the moment?

  20. TaduJR commented on Oct 21, 2026

    @TaduJR
    Contributor

    iOS have the same problem as android raised a pr here callstack/react-native-pager-view#1193

    Getting reviewed by @troZee

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2Engineering

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions