Files
BudgetWidgetTracker/README.md
2026-09-18 15:56:54 +10:00

87 lines
5.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Budget Tracker Widget
An Android home-screen widget that shows actual spend vs. budget: an overall
progress bar (color-coded green/amber/red) plus a per-category breakdown
chart, rendered for 7 sample budget items.
## Opening the project
1. Open Android Studio → **Open** → select the `BudgetTrackerWidget` folder.
2. Let Gradle sync (Android Studio will fetch Gradle 8.7 per
`gradle/wrapper/gradle-wrapper.properties` — no wrapper jar is bundled, so
the first sync needs an internet connection).
3. Run the `app` module on an emulator or device (API 26+).
4. Long-press the home screen → **Widgets** → find **Budget Tracker** → drag
it onto the home screen.
## How it's wired together
- **`BudgetItem.kt`** — data model for one budget line (category, budgeted, spent).
- **`BudgetRepository.kt`** — the single data source. `fetchBudgetData()` is a
placeholder that simulates a network call and returns 7 sample categories
(Rent, Groceries, Dining Out, Transport, Utilities, Entertainment, Savings)
mixing under-, at-, and over-budget items. A commented-out `parseBudgetJson()`
shows how to parse a real API response — swap the body of `fetchBudgetData()`
for an OkHttp/Retrofit/Ktor call when you have a real endpoint, and nothing
else in the app needs to change.
- **`BudgetColors.kt`** — the single green/amber/red threshold definition, shared
by the chart renderer, the widget's progress bar, and the app's progress bar,
so status colors are always consistent.
- **`BudgetChartRenderer.kt`** — draws the category bar chart onto a `Bitmap`
using `Canvas`. Widgets can't host arbitrary custom Views (RemoteViews only
supports a fixed set of native widgets), so this is the standard way to get
a custom visualization into a widget: draw it yourself, then push the
bitmap into an `ImageView`. The **app screen reuses this exact same
renderer**, so the in-app view and the widget are pixel-identical, just at
different sizes.
- **`WidgetLayoutPlanner.kt`** — pure logic that turns a widget's current
width/height (dp) into a display plan: whether to show the percent label,
whether there's room for the chart at all, how many category rows fit, and
whether text should shrink for narrow widths.
- **`BudgetWidgetProvider.kt`** — the `AppWidgetProvider`. Reads the widget's
live size from `onAppWidgetOptionsChanged()` (fired whenever the user drags
the resize handles) as well as `onUpdate()`, feeds it through
`WidgetLayoutPlanner`, and builds `RemoteViews` accordingly — hiding the
chart entirely at very small sizes, showing only the most over-budget
categories (with a "+N more" footer) when space is tight, and sizing the
rendered bitmap to match.
- **`widget_budget.xml`** — the widget layout: title, total, percent label,
three stacked (mutually-exclusive-visibility) `ProgressBar`s for the
green/amber/red states, an `ImageView` for the chart bitmap, and a "+N
more" footer.
- **`MainActivity.kt`** / **`activity_main.xml`** — a full-screen version of
the same card: same background, same title/total/percent, a single
`ProgressBar` tinted at runtime via `BudgetColors`, and the same chart
bitmap (rendered larger, showing every category since there's no space
constraint).
## Flexible widget sizing
The widget can be resized from a small ~2×1 cell (just title, total, and
progress bar) up to a full card with the complete chart:
- `minWidth`/`minHeight` in `budget_widget_info.xml` set the default placed
size; `minResizeWidth`/`minResizeHeight` set how small it can be dragged.
- At runtime, `onAppWidgetOptionsChanged()` reports the new size, and
`WidgetLayoutPlanner` decides what fits — hiding the percent label and
chart below certain heights, shrinking text below a certain width, and
showing the categories closest to (or over) budget first when not all 7
fit.
- Try dragging the widget to different sizes on the home screen to see it
adapt live.
## Extending it
- **Real data**: replace `BudgetRepository.fetchBudgetData()`'s body with an
actual HTTP call; keep the return type `List<BudgetItem>` and everything
downstream (widget + activity) keeps working unchanged.
- **Refresh cadence**: `updatePeriodMillis` in `budget_widget_info.xml` is set
to 30 minutes (Android's minimum useful floor is ~30 min for this
mechanism). For more frequent updates, trigger `ACTION_APPWIDGET_UPDATE`
yourself from a `WorkManager` periodic job.
- **Tap-to-open categories**: `setOnClickPendingIntent` is only wired to the
whole widget right now; you could add per-row click targets if you split
the chart into individual `ImageView`/`TextView` rows instead of one
bitmap.
- **Android 12+ responsive layouts**: for an even smoother resize experience
on API 31+, you could additionally supply a size-keyed
`RemoteViews(Map<SizeF, RemoteViews>)` so the system switches layouts
instantly during a drag, rather than waiting for the
`onAppWidgetOptionsChanged()` callback. The current approach already
covers all API levels (26+) and updates immediately once the drag ends.