Skip to content

[$175] Android - Plaid login page rotates to landscape instead of remaining in portrait #106342

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: v9.5.34-0
Reproducible in staging?: Yes
Reproducible in production?: Yes
If this was caught during regression testing, add the test name, ID and link from BrowserStack: #105598
Email or phone of affected tester (no customers): setowskiw+70@gmail.com
Issue reported by: Applause Internal Team
Bug source: Pull Request QA execution
Device used: Samsung Galaxy S21 FE/Android 16
App Component: Other

Action Performed:

  1. Launch Expensify app
  2. Tap Profile
  3. Tap Wallet
  4. Tap Add bank account, select United States and tap Regions Bank
  5. Tap the Online ID field
  6. Rotate the device to landscape
  7. Exit Plaid and reopen it while the device is in landscape

Expected Result:

Plaid remains in portrait orientation on Android phones, including when reopened while the device is in landscape.

Actual Result:

Plaid rotates to landscape along with the device. When reopened in landscape, Plaid also displays in landscape instead of portrait.

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7308232_1794134232893.105598_and.mp4

View all open jobs on GitHub

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

Upwork Automation - Do Not Edit

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Nov 9, 2026
  2. applause-bot commented on Nov 9, 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/5a2583566b4a5ae52084ad10f996cc97d5effc40e1ac7d7b4179dd4ca014b731

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Nov 9, 2026
  4. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @brunovjk (External)

  5. changed the title [-]Android - Plaid login page rotates to landscape instead of remaining in portrait[/-] [+][$175] Android - Plaid login page rotates to landscape instead of remaining in portrait[/+] on Nov 9, 2026
  6. melvin-bot commented on Nov 9, 2026

    @melvin-bot
  7. MelvinBot commented on Nov 9, 2026

    @MelvinBot
    Contributor

    🤖 Web test steps verification results: ⚠️ partial

    Verified app, Profile, and Wallet on web. An emailed security-code gate blocked bank selection; Android orientation cannot be validated on web.

    Account-validation security-code gate blocks bank selection

    Step Status
    1. Launch appHome snapshot confirmed the app was running.
    ✅
    2. Open ProfileSnapshot confirmed Profile.
    ✅
    3. Open WalletSnapshot confirmed Wallet and Add bank account.
    ✅
    4. Add bank account → United States → Regions BankAccount validation required an emailed security code. Country and bank selection were not reached.
    Account-validation security-code gate blocks bank selection
    ⚠️
    5. Tap Online IDBlocked by account validation; the field was not reached.
    ⚠️
    6. Rotate Android phone; Plaid remains portraitCannot verify native Android orientation on web.
    ⚠️
    7. Reopen Plaid while landscape; Plaid opens portraitCannot verify native Android reopening behavior on web.
    ⚠️

    Issue Analysis

    ⚠️ Preliminary analysis — root cause remains unconfirmed.

    Finding: The reported-version App source already explicitly locks Plaid to portrait on phones. Adding another portrait request is therefore not a verified solution. The unresolved boundary is whether the tested native build executes that policy and whether its orientation request is subsequently overridden.

    Code evidence and hypothesis checks
    • In the reported staging tag, MainApplication.kt:70–83 recognizes Plaid’s activity and applies the orientation policy when it opens.
    • MainApplication.kt:126–142 reapplies the policy on configuration changes: below 600dp, it requests portrait; at or above 600dp, it permits rotation. The Android 8.0 exception does not explain this Android 16 report.
    • Current main contains the same policy, not a later correction.
    • Keep Plaid in portrait on Android phones so its inputs stay visible attempted to restrict existing landscape behavior; the evidence does not establish that it introduced this failure.
    • Alternative explanations remain unverified: the shipped policy is not executed, the device enters the ≥600dp branch, or the SDK/platform overrides the request. Android 16 alone does not establish which occurred.

    Recommended next investigation: An internal engineer should inspect the tested Android build and capture the active Plaid activity, effective screen-width classification, and requested orientation during opening, rotation, and reopening. This distinguishes the remaining explanations before selecting a fix. Backend logs would not establish those native activity states.

    Related issue: Wallet—in landscape, input hidden and cursor shifts right after typing tracks the underlying landscape-input problem this portrait restriction intended to address.


    Next Steps: Reply with @MelvinBot <your feedback> to refine this analysis.


    view run

  8. kevinjosh10 commented on Nov 9, 2026

    @kevinjosh10

    Proposal

    What is the root cause of that problem?

    In android/app/src/main/java/com/expensify/chat/MainApplication.kt:
    applyPlaidOrientation is currently only invoked inside onActivityCreated (and in Application.onConfigurationChanged if the configuration changes while Plaid is alive).

    However:

    1. LinkActivity in AndroidManifest.xml is declared with android:launchMode="singleTask":
      <activity
          android:name="com.plaid.internal.link.LinkActivity"
          android:launchMode="singleTask"
          android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
          tools:replace="android:launchMode"/>
    2. When a user exits Plaid, rotates the device to landscape, and re-opens Plaid from Expensify, LinkActivity is restarted or resumed from the task stack (onRestart -> onStart -> onResume). Because the activity was already instantiated, onActivityCreated never fires again.
    3. Because onActivityStarted and onActivityResumed in MainApplication.kt are currently empty no-op stubs (override fun onActivityStarted(activity: Activity) {}), applyPlaidOrientation is never re-applied when Plaid is reopened while the host app is in landscape.
    4. Furthermore, if plaidLinkActivity was cleared or not refreshed, Application.onConfigurationChanged cannot re-assert portrait lock on the active instance. Consequently, Plaid inherits the landscape window orientation, causing the keyboard to obscure the login inputs on phones.

    What changes do you think we should make in order to solve the problem?

    In android/app/src/main/java/com/expensify/chat/MainApplication.kt:

    1. In registerActivityLifecycleCallbacks:
      • Store/refresh the plaidLinkActivity weak reference and invoke applyPlaidOrientation in onActivityStarted and onActivityResumed as well as onActivityCreated:
      override fun onActivityStarted(activity: Activity) {
          if (activity.javaClass.name == "com.plaid.internal.link.LinkActivity") {
              plaidLinkActivity = WeakReference(activity)
              applyPlaidOrientation(activity, activity.resources.configuration)
          }
      }
      
      override fun onActivityResumed(activity: Activity) {
          if (activity.javaClass.name == "com.plaid.internal.link.LinkActivity") {
              plaidLinkActivity = WeakReference(activity)
              applyPlaidOrientation(activity, activity.resources.configuration)
          }
      }
    2. In applyPlaidOrientation:
      • Ensure the portrait lock is strictly enforced on all phones (config.smallestScreenWidthDp < 600) whenever the activity is active, started, or resumed, while preserving landscape for tablets:
      private fun applyPlaidOrientation(activity: Activity, config: Configuration) {
          if (config.smallestScreenWidthDp >= 600) {
              activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
              return
          }
          // Android 8.0 throws when a translucent activity requests a fixed orientation; Plaid skips it there too.
          if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
              activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
          }
      }
    3. HybridApp parity note: Since native activity lifecycle callbacks in MainApplication.kt mirror across NewDot and Mobile-Expensify, this fix applies cleanly to both standalone and hybrid builds without introducing regression risks to non-Plaid activities.

    What alternative solutions did you explore? (Optional)

    Attempting to lock orientation from the React Native JS layer via orientation hooks was considered. However, Plaid's LinkActivity runs in a native Android activity layer outside the React Native view hierarchy. Centralizing the lifecycle listener inside MainApplication.kt's registerActivityLifecycleCallbacks (onActivityStarted & onActivityResumed) ensures Plaid is unconditionally locked to portrait on Android phones across all open, reopen, resume, and rotation transitions.

    Tests

    1. On an Android phone, launch the Expensify app.
    2. Tap Profile > Wallet.
    3. Tap Add bank account, select United States, and tap Regions Bank.
    4. On the Plaid login screen, tap the Online ID field and rotate the device to landscape.
      • Verify Plaid remains in portrait orientation and the Online ID field remains clearly visible above the keyboard.
    5. Tap back to exit Plaid, rotate the device to landscape.
    6. Reopen Plaid while the device is in landscape.
      • Verify Plaid opens and remains in portrait orientation without rotating to landscape or showing a white page.
    7. On an Android tablet, repeat steps 1–4 and verify Plaid can rotate to landscape as expected.

  9. TassioSales commented on Nov 9, 2026

    @TassioSales

    Contributor details
    Your Expensify account email: tassiolucian.ljs@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~0179cdc82689f205c7

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On Android, opening the Plaid bank authentication webview/activity allows the screen to rotate to landscape orientation instead of remaining locked in portrait mode.

    What is the root cause of that problem?

    The Android activity configuration launched for Plaid Link (PlaidLinkActivity or external auth browser activity) does not specify android:screenOrientation="portrait" in AndroidManifest.xml, allowing system auto-rotation settings to override the app's portrait lock during banking flows.

    What changes do you think we should make in order to solve the problem?

    1. In android/app/src/main/AndroidManifest.xml, ensure screenOrientation="portrait" is explicitly defined for all Plaid/OAuth activities:
      <activity
          android:name="com.plaid.link.PlaidLinkActivity"
          android:screenOrientation="portrait"
          android:configChanges="orientation|screenSize" />
    2. In src/libs/Plaid/index.android.ts, enforce ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT) prior to opening the Plaid Link dialog.

    What alternative solutions did you explore? (Optional)

    • Allowing landscape for Plaid: Rejected because Plaid's UI components and Expensify's parent modal are designed specifically for portrait, causing truncated form elements in landscape.

    What automated tests can you add to prevent this from re-occurring?

    In unit/integration tests:

    • Add a test verifying that launching the bank connection flow invokes lockAsync with PORTRAIT lock.
  10. melvin-bot commented on Nov 9, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. HindsightWise commented on Nov 10, 2026

    @HindsightWise

    Contributor details
    Your Expensify account email: hindsightwise@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~013d295f05a578824b

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    On Android phones:

    1. When opening Plaid bank login (Profile > Wallet > Add bank account > United States > [Bank]), focusing the credential input, and rotating the device to landscape, Plaid rotates to landscape along with the device.
    2. Exiting Plaid, rotating the device to landscape, and reopening Plaid from Expensify while in landscape results in Plaid displaying in landscape instead of remaining locked in portrait.

    What is the root cause of that problem?

    In android/app/src/main/java/com/expensify/chat/MainApplication.kt:

    1. Missing orientation enforcement on resume / restart (singleTask lifecycle):
      In android/app/src/main/AndroidManifest.xml:167-172, LinkActivity is declared with android:launchMode="singleTask" and android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode":

      <activity
          android:name="com.plaid.internal.link.LinkActivity"
          android:launchMode="singleTask"
          android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
          tools:replace="android:launchMode"/>

      When the user exits Plaid, the activity is stopped rather than destroyed. When Plaid is reopened from the host app while the device is in landscape, Android does not re-create the activity; it delivers an intent and runs onRestart() -> onStart() -> onResume().
      Because MainApplication.kt only calls applyPlaidOrientation in onActivityCreated—leaving onActivityStarted and onActivityResumed as empty stubs:

      override fun onActivityStarted(activity: Activity) {}
      override fun onActivityResumed(activity: Activity) {}

      onActivityCreated never fires on reopen, and applyPlaidOrientation is never re-applied. LinkActivity therefore inherits the landscape window configuration of the parent task.

    2. Plaid OAuth / WebView configuration changes:
      Because LinkActivity handles orientation|screenSize in android:configChanges, rotating the device while Plaid is open (e.g. on Regions Bank OAuth / credential webview) dispatches onConfigurationChanged directly to LinkActivity. If plaidLinkActivity was not refreshed upon resume or if the webview resets the orientation request upon loading, applyPlaidOrientation must be re-asserted throughout the activity's active lifecycle states (started and resumed) so the window manager consistently locks the orientation to portrait on phones.

    What changes do you think we should make in order to solve the problem?

    1. Update registerActivityLifecycleCallbacks in android/app/src/main/java/com/expensify/chat/MainApplication.kt:

      • In onActivityStarted and onActivityResumed, check if the activity is "com.plaid.internal.link.LinkActivity", refresh plaidLinkActivity = WeakReference(activity), and invoke applyPlaidOrientation(activity, activity.resources.configuration):
        override fun onActivityStarted(activity: Activity) {
            if (activity.javaClass.name == "com.plaid.internal.link.LinkActivity") {
                plaidLinkActivity = WeakReference(activity)
                applyPlaidOrientation(activity, activity.resources.configuration)
            }
        }
        
        override fun onActivityResumed(activity: Activity) {
            if (activity.javaClass.name == "com.plaid.internal.link.LinkActivity") {
                plaidLinkActivity = WeakReference(activity)
                applyPlaidOrientation(activity, activity.resources.configuration)
            }
        }
      • Retain the cleanup in onActivityDestroyed:
        override fun onActivityDestroyed(activity: Activity) {
            if (plaidLinkActivity?.get() === activity) {
                plaidLinkActivity = null
            }
        }
    2. Ensure applyPlaidOrientation maintains tablet landscape support:
      Keep the tablet check (config.smallestScreenWidthDp >= 600 -> ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) so tablet users continue to have full landscape support per PR Keep Plaid in portrait on Android phones so its inputs stay visible #105598, while locking phones (smallestScreenWidthDp < 600) to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT (with the Android 8.0 Build.VERSION.SDK_INT != Build.VERSION_CODES.O guard):

      private fun applyPlaidOrientation(activity: Activity, config: Configuration) {
          if (config.smallestScreenWidthDp >= 600) {
              activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
              return
          }
          if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
              activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
          }
      }

    What alternative solutions did you explore? (Optional)

    1. Hardcoding android:screenOrientation="portrait" on LinkActivity in AndroidManifest.xml:
    2. Locking orientation from React Native JS via ScreenOrientation.lockAsync:
      • Rejected because Plaid's LinkActivity is a native Android activity launched outside the React Native view hierarchy; JS-level orientation locks only control MainActivity (ReactActivity) and do not reliably govern Plaid's window manager parameters.

    Tests

    1. On an Android phone:
      • Launch the Expensify app.
      • Navigate to Profile > Wallet > Add bank account > United States > Regions Bank.
      • On the Plaid credential screen, tap the Online ID field and rotate the device to landscape.
      • Verify Plaid remains in portrait orientation and the Online ID field remains clearly visible above the keyboard.
      • Exit Plaid, rotate the device to landscape, and tap "Log into your bank" to reopen Plaid while the app is in landscape.
      • Verify Plaid opens and remains locked in portrait orientation without rotating to landscape or rendering a blank page.
    2. On an Android tablet:
      • Repeat the bank connection flow and verify Plaid can freely rotate to landscape as expected.

    — submitted with Antigravity on the contributor's behalf

  12. melvin-bot commented on Nov 10, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  13. brunovjk commented on Nov 11, 2026

    @brunovjk
    Contributor

    Hey @hoangzinh, I checked the Mobile-Expensify pins for 9.5.34-0 and 9.5.36-0. Both predate the merge of Expensify/Mobile-Expensify#14216. Could you confirm which Android HybridApp build includes that fix so QA can retest on the Galaxy S21 FE with Android 16? Thanks.

  14. hoangzinh commented on Nov 12, 2026

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

Metadata

Metadata

Assignees

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributor

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions