87 lines
5.0 KiB
Markdown
87 lines
5.0 KiB
Markdown
# 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.
|