Skip to content

ADFA-4327: Stop mem-usage chart LegendRenderer crash (disable legend)#1417

Draft
fryanpan wants to merge 1 commit into
stagefrom
ADFA-4327-memusage-chart-legendrenderer-crash
Draft

ADFA-4327: Stop mem-usage chart LegendRenderer crash (disable legend)#1417
fryanpan wants to merge 1 commit into
stagefrom
ADFA-4327-memusage-chart-legendrenderer-crash

Conversation

@fryanpan

@fryanpan fryanpan commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Jira Ticket: https://appdevforall.atlassian.net/browse/ADFA-4327
Sentry Issue: https://appdevforall-inc-9p.sentry.io/issues/APPDEVFORALL-31

Reproduction Details

The editor's live memory-usage chart (MPAndroidChart) crashes in LegendRenderer.renderLegend with an IndexOutOfBoundsException when the chart's dataset count changes at runtime — a Gradle daemon/tooling process connects, resetMemUsageChart rebuilds the LineData with a new dataset count, and the auto-computed legend then renders against a stale/desynced entry array.

Stack Trace

IndexOutOfBoundsException: Index 1 out of bounds for length 1
  at java.util.ArrayList.get(ArrayList.java:435)
  at com.github.mikephil.charting.renderer.LegendRenderer.renderLegend(SourceFile:924)
  at com.github.mikephil.charting.charts.BarLineChartBase.onDraw(SourceFile:418)
  ... (View draw / ThreadedRenderer) ...

(repro device: moto g56, device.class: high, Android 16 — not limited to low-end)

User Steps

User steps leading up to crash, based on Sentry breadcrumbs:

  • From the home screen the user taps a project to open it (MainActivityEditorActivityKt). As the editor loads and the mem-usage chart updates (Gradle daemon connecting → dataset count grows), the legend renders out of bounds during a draw pass.

Was able to reproduce in a unit test?

Yes — the fix's invariant is unit-tested. MemUsageLegendEntriesTest (Robolectric, 3/3 green) asserts the legend entries always match the datasets: one entry per dataset, label/color carried through, and the entry count tracking the dataset count across a 1→3 change (the exact condition that desynced the renderer). The literal draw-time IndexOutOfBoundsException is a race inside the third-party renderer that is not deterministically reproducible on the JVM, so it is verified on-device (see Testing).

What Was Fixed

Keep the legend visible and set its entries explicitly so MPAndroidChart never lazily re-computes a stale entry list when the dataset count changes — this handles dataset item-length changes without disabling the legend:

  • New top-level buildMemUsageLegendEntries(datasets) builds one LegendEntry per dataset (label + line color).
  • legend.setCustom(...) is called with those entries in resetMemUsageChart (when the dataset count changes) and in the live-update listener (so the "<name> - <MB>" labels stay current) — keeping the legend's entry list always consistent with the datasets.

The legend remains fully functional; nothing is hidden from the user.

Testing

  • Unit: MemUsageLegendEntriesTest (Robolectric) — 3/3 green (:app:testV8DebugUnitTest).
  • On-device (Samsung A56, Android 16): built this fix, opened a project, ran 2× Sync + :app:assembleDebug — each connects the Gradle daemon (the dataset-count change that crashed pre-fix). No LegendRenderer / IndexOutOfBoundsException / FATAL in logcat, app stayed alive, and the chart's legend renders correctly (screenshot + recording on the PR).

Fixes APPDEVFORALL-31

@fryanpan fryanpan force-pushed the ADFA-4327-memusage-chart-legendrenderer-crash branch from c4a99e7 to 464fdea Compare June 19, 2026 12:19
@fryanpan

Copy link
Copy Markdown
Contributor Author

On-device verification — Samsung A56 (Android 16)

Built CoGo v8 debug with this fix (versionName C-d-0619-0528), installed on the A56, opened a project, and exercised the exact crash trigger — Gradle work that connects the daemon and changes the memory-usage chart's dataset count (resetMemUsageChart rebuilds the LineData with a new count → the desync that crashed LegendRenderer pre-fix):

  • 2× project SyncCONFIGURE SUCCESSFUL
  • :app:assembleDebug (Tooling-API buildId 3) → ran through the full task graph

Result: across all three daemon-connect cycles —

  • No LegendRenderer / IndexOutOfBoundsException / FATAL EXCEPTION in logcat (cleared before the run).
  • App stayed alive the whole time (single pid, never died/restarted).
  • The memory-usage chart updates across the dataset-count changes with the legend enabled — no crash.

This is the runtime counterpart to the green MemUsageLegendEntriesTest (which pins the anti-desync invariant the literal draw-time IOOBE can't be deterministically reproduced in JVM). Screen recording of the build session attached on the PR.

@fryanpan

Copy link
Copy Markdown
Contributor Author

⚠️ Caveat: crash fixed, but the chart/legend is currently unreachable by users

While verifying on-device I found the memory-usage chart this PR fixes is dead/unreachable UI in the current build — a user has no way to actually see the legend. Three separate issues in SwipeRevealLayout:

  1. Touch isn't wired to the revealonInterceptTouchEvent/onTouchEvent only drive the left/right drawer helpers; the vertical dragHelper that reveals the chart is never fed touch events, so no swipe can open it.
  2. open()/toggleState can't animate — they call dragHelper.smoothSlideViewTo(...), but computeScroll() only calls continueSettling() on the left/right helpers, never dragHelper, so the slide never progresses.
  3. Nothing calls open()/toggleState — the only swipeReveal usage in the app is close() on back-press.

So the chart only ever draws behind the editor card — which is exactly where this crash originated (LegendRenderer.renderLegend runs every frame even though the chart is never visible to the user). To screenshot the legend for verification I had to force it on top via elevation in a throwaway debug build (not in this PR — 464fdea39 is the clean fix only).

This PR correctly stops the background crash. Whether to wire up the reveal or remove the chart entirely (a LineChart redrawing every frame behind the editor on a low-end target, for UI no one can reach) is a separate fix-or-remove decision to raise with the team.

MPAndroidChart's LegendRenderer threw IndexOutOfBoundsException when its
auto-computed legend desynced from the dataset count as that count changed at
runtime (Gradle daemons connecting -> resetMemUsageChart rebuilds the datasets
with a new count).

Keep the legend visible (no UI removal; replaces the earlier legend.isEnabled =
false approach) and set its entries EXPLICITLY via legend.setCustom(...) in both
resetMemUsageChart and the live-update listener, so the renderer never lazily
recomputes a stale entry list against a changed count. The entry builder is a
top-level internal fun (buildMemUsageLegendEntries) so it is unit-testable
without an Activity.

Test: MemUsageLegendEntriesTest (Robolectric) pins the anti-desync invariant -
one entry per dataset, label/color carried through, and the entry count tracking
the dataset count across a count change. The literal draw-time
IndexOutOfBoundsException is a race inside the third-party renderer, verified
on-device (recording on the PR).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fryanpan fryanpan force-pushed the ADFA-4327-memusage-chart-legendrenderer-crash branch from 464fdea to dbffc52 Compare June 19, 2026 16:58
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.

1 participant