B4Due
B4Due is a bill and subscription reminder for Android, built for Indonesian households: electricity, BPJS, vehicle tax, instalments, streaming, and subscriptions billed in dollars. It reminds you 7, 3 and 1 day before a bill is due and on the day itself, and you can mark the bill as paid straight from the notification. A calendar view and spending statistics show where the money goes. There is no account and no server, so it costs nothing to run for anyone. The interface ships in Bahasa Indonesia and English, in light and dark mode.
01 / 06Home Light
- Accounts or Servers
- 0
- Accounts or Servers
- Automated Tests
- 148
- Automated Tests
- Foreign Currencies
- 16
- Foreign Currencies
- Bill Templates
- 23
- Bill Templates
- Languages (ID, EN)
- 2
- Languages (ID, EN)
The problem
Bill reminders only matter if they fire. Without a server there is no push service to fall back on, so every reminder has to be scheduled on the device itself, survive reboots, and get past the aggressive battery savers on Xiaomi, OPPO, vivo and similar phones. The "Mark as paid" button on a notification also runs while the app is closed, in a separate isolate, and must never record the same payment twice. And because many subscriptions are billed in dollars, the app needs current exchange rates without giving up its offline, account-free design.
Scheduling is a pure function: bills, settings and the current time go in, and a list of notifications with deterministic IDs comes out. A sync step reconciles the OS queue against that list, so re-running it is idempotent and can never duplicate a reminder. Notification actions share one Drift database server across isolates and pay a bill inside a single idempotent transaction. Recurring dates are civil dates on an anchor day, so a bill due on the 31st stays on the 31st after February. Exchange rates are an anonymous request to the European Central Bank's reference rates, cached locally, so reminders, totals and statistics keep working in rupiah when the phone is offline.
Architecture
How it fits together
01
Pure Scheduling Planner
planNotifications() turns bills, settings and the current time into reminders keyed by bill, due date and offset, hashed to stable IDs. ReminderSync cancels what is no longer planned and reschedules the rest on launch, resume, every data change, restore, and a 12-hourly WorkManager task.
02
Background Isolate Actions
Notification actions run with the app closed. All isolates connect to one shared Drift database server, and paying a bill is a single write-locked transaction that ignores double taps, duplicate deliveries, and stale notifications for an earlier period.
03
Offline First, Private by Default
No account, backend or analytics. The only network call is a GET for exchange rates that carries no user data, with a timeout, size cap and local cache. Backups are JSON files the user keeps in their own storage, and CI rejects any APK that asks for a permission outside an allowlist.
Stack
What each piece does
- 01
Flutter + Riverpod
A feature-first Flutter app with Riverpod for state and a five-tab go_router shell. Domain code has no Flutter imports, so date rules and the scheduler are unit-tested without a device.
- 02
Drift / SQLite
Local SQLite through Drift with UUID keys, soft deletes, and step-by-step migrations tested from every earlier schema version with real rows. SQLite is compiled from a vendored amalgamation so release builds download no prebuilt binaries, as F-Droid requires.
- 03
Local Notifications
flutter_local_notifications with exact alarms schedules a rolling horizon of reminders (120 days on Android, 64 notifications on iOS). Per-brand battery guidance, a test-notification button, and optional calendar alarms cover phones that kill background work.
- 04
Background Isolates
Notification actions and WorkManager tasks run in their own isolates but connect to one shared Drift server, so a payment made from the notification updates the open home screen immediately. Tests run six simultaneous deliveries of the same action against one SQLite file.
- 05
Exchange Rates
Bills can be priced in 16 foreign currencies. Daily ECB reference rates come from Frankfurter, are cached in SQLite and refreshed every 12 hours, and each foreign bill keeps its original amount so the rupiah estimate is recomputed after every refresh.
- 06
GitHub Actions
CI runs analyze, unit, widget, migration and golden tests in light and dark mode, then audits the built APK's permissions. release-please cuts semver releases from Conventional Commits, every green build on main ships a signed nightly APK, and users can auto-update through Obtainium.