Skip to content

[Due for payment 2026-10-20] [Exploratory] Chart- Inline chart is empty when minimize and reopen app #101396

Closed

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.81-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: #99661
Email or phone of affected tester (no customers): duytestnguyen+384@gmail.com
Issue reported by: Applause Internal Team
Bug source: Exploratory - Significant User Experience Deterioration
Device used: Android 16 / Pixel 7A
App Component: Other

Action Performed:

Preconditions:

  • Use account that has many expenses from previous days, months
  1. Open Hybrid app
  2. Navigate to Concierge chat
  3. Ask for "Top spenders", "Top categories"...
  4. Tap on "expand" button in the inline chat
  5. Miminize the app
  6. Reopen the app

Expected Result:

The chart should be displayed after reopen the app

Actual Result:

The chart disappears

Workaround:

Unknown

Platforms:

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

Screenshots/Videos

Bug7262037_1789613924571.Minimize.mp4

View all open jobs on GitHub

Applause Internal Information
Exported by: Mita Rachim
Bug ID: 7262037

Issue OwnerCurrent Issue Owner: @aimane-chnaif

Activity

  1. applause-bot commented on Sep 17, 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/7eeb6c9d41c6a101271204ea83cb512550677fb0adcb9134ccf2c01cc934a0d5

  2. situchan commented on Sep 17, 2026

    @situchan
    Contributor

    #97698 faced on Android with #101396

    @MelvinBot investigate this

  3. MelvinBot commented on Sep 17, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The chart data is fine — it's the Skia canvas that never repaints.

    When Android backgrounds the app, the Skia TextureView backing the chart destroys its surface. On resume the surface is recreated, but nothing on the JS side pushes a new picture into it, so the canvas comes back blank. This is the long-standing upstream bug Shopify/react-native-skia#2135 ("[android] Canvas disappear in Modal when app is resumed from background"), still open.

    Nothing in App recovers from it. There is no AppState listener anywhere in the chart path — grep -rn "AppState" src/components/Charts src/components/HTMLEngineProvider/HTMLRenderers/VictoryChartRenderer src/components/MultiGestureCanvas returns zero matches. Since the chart props are static, React never re-renders it, so the canvas is never asked to redraw.

    Why tapping "expand" first is part of the repro: VictoryChartExpandable.tsx:70 latches shouldRenderModal to true on first expand and deliberately keeps the modal mounted after close (comment at :28-30). From that point the chart lives under an Android Modal, which is a separate window — exactly the configuration #2135 is filed against.

    Three things I checked and ruled out:

    What is NOT the cause

    Secondary contributor (not fully verified): the inline chart is the only variant whose canvas size comes from ephemeral layout state — getChartLayoutModeProps.ts:6-12 returns {} when there's no explicitSize, so victory-native measures via a single onLayout into useState. A 0×0 measurement while the activity is stopped behind a fullscreen dialog would stick. The expanded chart passes explicitSize and bypasses this. This matches the title saying inline, but I could not prove Android delivers that 0×0 layout without a device repro.

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

    Remount the chart's Skia Canvas when the app returns to the foreground — the same workaround the App already uses for Lottie, which breaks the same way.

    src/components/Lottie/index.tsx reads useAppState() at :31, renders a plain placeholder View while appState.isBackground at :110, and forces a fresh mount via a key prop at :128.

    Apply the same shape in the chart renderer: hook useAppState, bump a counter each time the app goes background → active, and pass it as key on the chart container so the Canvas unmounts and remounts with a live surface. This needs to cover both the inline chart and the one inside VictoryChartExpandModal, since both sit on Skia canvases.

    Remounting is cheap here — the chart model is re-derived from the HTML anyway, and the font cache is module-level so no typefaces are reloaded.

    What alternative solutions did you explore?

    • Set androidWarmup on the Canvas. RN Skia's androidWarmup path blits the picture into a plain Android Bitmap via onDraw, independent of the TextureView surface, and the patch-003 canvasProps passthrough would already carry it. Its documented purpose is first-frame warmup, so I'm not confident it survives surface loss — worth a quick try since it's a one-line change, but I wouldn't lead with it.
    • Unmount the expand modal on close instead of latching shouldRenderModal. This removes the Modal window that Update version to 1.0.7-2 on master #2135 is specifically about, but the comment at :28-30 says keeping it mounted is a deliberate perf choice, and it wouldn't help the inline canvas if that one blanks too.
    • Wait for the upstream fix. Update version to 1.0.7-2 on master #2135 has been open since January 2024 with no fix. Not viable.
    What I could not verify

    I could not reproduce this on a device — this run is scoped to web, and the bug is Android-app-only, so a browser cannot exercise the TextureView surface-destroy path at all. Two things worth confirming before implementing:

    1. Does the blank canvas in the video belong to the inline chart or the still-open expand modal? Both share the root cause and the fix, but it determines which one to test first.
    2. Does it still reproduce if you never tap expand? If yes, the inline canvas blanks on its own and the fix must cover it independently of the modal. If no, the Modal window is the trigger.

    adb logcat filtered on SkiaTextureView/SkiaView during the repro would show whether onSurfaceTextureAvailable fires on resume and paints nothing, versus never firing.


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/101396) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run

  4. abbasifaizan70 commented on Sep 17, 2026

    @abbasifaizan70
    Contributor

    @situchan @luacmartins Can i investigate it?

  5. situchan commented on Sep 18, 2026

    @situchan
    Contributor

    @MelvinBot draft PR for testing. I'll validate if remount solution works.

  6. MelvinBot commented on Sep 18, 2026

    @MelvinBot
    Contributor

    🤖 Draft PR created: Remount chart Skia canvas when the app returns to the foreground

    Please mark it as "Ready for review" when it is ready for review.

    What it does: a new useSkiaCanvasRemountKey hook returns a value that changes on every background → active transition, and that value is passed as a key on the chart so the Skia Canvas remounts against a live surface. Same shape as the existing Lottie workaround. On web the hook returns a constant, so nothing changes there.

    Two things to look at while you validate:

    1. The key is applied inside VictoryChartContent and VictoryChartInteractiveContent, which covers all three mount sites — inline chart, the fluid chart in VictoryChartExpandModal, and ExpandedChartBox. So it should recover whichever canvas is actually blanking, which also answers the open question from the analysis about which one it is.
    2. Only a full background transition bumps the key, not inactive. If it turns out Android blanks the canvas on a transition that never reports background, the key won't fire and you'll see no change — that's the most likely way this fails.

    I could not verify it. The bug is Android-app-only and this run was scoped to web, so no device repro. The web test session also failed during onboarding, so there's no App-level verification at all in the PR. Local checks that did pass: lint, typecheck, React Compiler compliance (both Babel and OXC), cspell, and 307 chart tests including 4 new ones for the hook.


    view run

  7. melvin-bot commented on Sep 21, 2026

    @melvin-bot

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

  8. 25 remaining items

  9. melvin-bot commented on Oct 19, 2026

    @melvin-bot

    Payment Summary

    Resolving PRs:

    BugZero Checklist (@mallenexpensify)

    • I have confirmed assignees, roles, and Upwork contracts look correct
    • I have paid out Upwork contracts / manual NewDot requests
    • [BugZero Assignee] I have created a GH issue for creating/updating the regression test once above steps have been agreed upon
  10. mallenexpensify commented on Oct 20, 2026

    @mallenexpensify
    Contributor

    Payment Summary

    Contributor: @situchan due $250 via NewDot
    Contributor+: @aimane-chnaif due $250 via NewDot

    @aimane-chnaif plz complete the Contributor+ checklist and tag me in a post once you have. Thx

  11. situchan commented on Oct 20, 2026

    @situchan
    Contributor

    @mallenexpensify I am also eligible for payment as PR author.

  12. mallenexpensify commented on Oct 20, 2026

    @mallenexpensify
    Contributor

    @situchan paid, summary updated above. Thx, sorry for missing that

  13. situchan commented on Oct 20, 2026

    @situchan
    Contributor

    @mallenexpensify thanks. Should the amount be $250?

  14. aimane-chnaif commented on Oct 20, 2026

    @aimane-chnaif
    Contributor

    Contributor+ Checklist:

    • [Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.

      Link to the comment on the PR:
      Link to the comment on the Issue:

    • [Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.

      Link to discussion:

    • [Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.

    Regression Test Proposal

    Precondition:

    an account with expenses from earlier days and months.

    Test:

    1. On Android, open Concierge and ask for "Top categories" (pie chart).
    2. Tap the expand button on the chart.
    3. Minimize the app, then reopen it.
    4. Verify the expanded chart is still displayed.
    5. Repeat steps 3-4, locking and unlocking the screen instead of minimizing.
    6. Repeat steps 1-4 with "Top spenders" (bar chart).
    7. Close the expanded chart, minimize and reopen the app, and verify the inline chart is still displayed.

    Do we agree 👍 or 👎

  15. mallenexpensify commented on Oct 21, 2026

    @mallenexpensify
    Contributor

    @situchan yup, apologies, missed it. Price updated to $250

    Image
  16. garrettmknight commented on Oct 30, 2026

    @garrettmknight
    Contributor

    $250 approved for @aimane-chnaif

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

Metadata

Metadata

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions