ADR-0028: ComposeCharts for report visuals
- Status: Accepted
- Date: 2026-08-22
- Deciders: maintainers
- Supersedes: ADR-0013
- Related: ui.md, issue #51
Context
ADR-0013 hand-rolled the reports donut and trend bars with Compose Canvas.
That kept dependencies at zero but left animations, gridlines, axis/value
labels and adaptive sizing unimplemented, and every new chart type would
re-implement drawing + gesture + tooltip logic by hand.
Decision
Report charts are rendered with
io.github.ehsannarmani:compose-charts
(Apache-2.0):
- Category breakdown →
PieChartwithPie.Style.Stroke(donut). - 6-month trend →
ColumnChartwith gridlines and a base-currency value scale.
Version is 1.0.0 (amended 2026-08-22). The original pin to 0.2.0
existed because 0.2.5+ releases are compiled with Kotlin 2.3.10, which the
then-toolchain (Kotlin 2.2.10 embedded via AGP 9.x built-in Kotlin) could not
consume. The toolchain has since lifted KGP (now 2.4.21) through AGP's
documented "upgrade to a higher KGP version" route (root build.gradle.kts
classpath), unpinning the library. Version bumps must still re-check the
Kotlin metadata requirement against the toolchain before upgrading; upstream
1.0.0 enables its pie label helper by default, so the donut passes
LabelHelperProperties(enabled = false) to keep the legend app-side.
App-side responsibilities stay app-side:
- Donut total (centered when it fits, above the ring otherwise), selection bubble (
ChartTooltip) and legend rows (icons) — 0.2.0'sPieCharthas no popup or hole text. - Trend month labels and per-bar tap bubbles — the library draws canvas labels
as pixels (invisible to TalkBack); labels are Compose
Texts below the chart, taps land on an overlay row of per-bar cells that also carry the accessibility summaries. - All colors derive from
MaterialTheme.colorSchemeviareportChartPalette()/ screen-level colors — nothing hardcoded. - Mapping to library models lives in pure functions
(
ui/reports/charts/ChartAdapters.kt), JVM-unit-tested; money strings are always formatted fromLongminor units throughMoneyFormatter, never from the library'sDouble.
Amended 2026-10-15 (issue #117): measure the total label and amount at the
user's font scale using their actual text styles. Keep them in the 104 dp
center area only when both natural widths and their combined height fit.
Otherwise place the total above the ring, allowing wrapping at the available
width. Use the same titleLarge amount style in either position; never shrink
text or truncate the financial value to fit the hole. Chart geometry, tooltip
bounds, selection, and accessible summaries stay independent of this layout.
Library internals (arc math, animation timing) are not tested.
Consequences
- Charts animate on entry; trend gains gridlines and a value scale.
- One extra dependency (+32 KiB APK, R8-minified, no extra keep rules).
- Version bumps must re-check the Kotlin metadata requirement against the toolchain before upgrading.
common/ChartGeometryhit-testing was deleted with its tests;ChartTooltipremains (donut selection).
Alternatives considered
- Keep hand-rolled Canvas (ADR-0013) — rejected: per-chart reimplementation of interaction/accessibility for every new visual.
- Vico / MPAndroidChart — rejected: View-interop or heavier theming than a pure-Compose KMP library for two chart types.