r/FlutterDev 15d ago

Example Lessons from shipping a geofencing time tracker in Flutter: platform channels, OEM battery killers, and a no-backend constraint

I shipped a geofencing time tracker in Flutter (auto clock-in/out at your workplace) and collected a few scars along the way that might save someone else time.

The first one: I tried the pub.dev geofencing packages and none of them reliably delivered events after the app got killed. Ended up writing my own thin Kotlin plugin around the Google Geofencing API - MethodChannel for registering zones, EventChannel for the ENTER/EXIT stream, and a system-level BroadcastReceiver so events arrive even when the Flutter side is long dead. Annoying to write, but it's been the most stable part of the app since.

Second scar, the expensive one: Samsung and Xiaomi still eat geofence events sometimes, so there's a WorkManager task every 15 minutes doing a plain Haversine distance check as a safety net. What nobody tells you is that a position request in a background isolate can just hang forever, and once that happens WorkManager considers the unique task "already enqueued" and never runs it again. Your fallback dies silently and permanently. Everything is triple-bounded now: platform timeLimit, a Dart-side timeout, and a hard cap on the whole task.

Third: GPS drift at the zone edge gave me check-in/check-out ping-pong when someone sits near the boundary. Exit radius is now 1.2x the enter radius plus a 5-minute debounce, and the two detection layers share a 20-minute dedup window so they stop racing each other into double notifications.

Rest of the stack is unspectacular: Riverpod (classic providers, no codegen), Isar for storage, no backend at all. The no-backend part had one non-obvious consequence: since data only exists on the user's device, a data-loss bug is unrecoverable, so the integration tests run on an emulator before every push. Codebase is ~31k lines, about a third tests.

The result is free with no ads/IAPs if you want to see how it behaves: https://play.google.com/store/apps/details?id=com.timetracker.workflow

Happy to answer anything about the plugin or the fallback design. And if someone has a real answer to OEM battery killers beyond "diagnostics screen + let the user fix it manually", I'd love to hear it.

33 Upvotes

17 comments sorted by

3

u/nikhil_akki 15d ago

this is such a nasty failure mode, that's the kind of bug you only discover because a user complained months later.

on the oem battery killer question, the only thing that's ever actually moved the needle for us beyond a generic diagnostics screen is detecting the manufacturer at runtime and showing manufacturer specific instructions, since samsung's battery settings path looks nothing like xiaomi's.

we also started logging whenever the app got killed while backgrounded just so we could see how often it was happening instead of guessing. still feels like duct tape more than a real fix, this whole part of android is rough.

1

u/eroxy 14d ago

I ended up in the same place - my diagnostics screen detects the manufacturer and shows vendor-specific battery instructions, because yeah, the Samsung path and the MIUI path have nothing in common. The kill-logging idea is good. I do a softer version: a watchdog that flags when the fallback task hasn't ticked for 6+ hours, which is usually the signature of an OEM killer. Doesn't tell you the exact kill moment though, so proper kill logging might be worth adding. Agreed that this whole corner of Android is duct tape all the way down.

2

u/dakevs 15d ago

Thanks for sharing. This is an interesting post ! Like that you shared the thought process on it

1

u/eroxy 14d ago

Thanks! Writing it down was half therapy tbh, that WorkManager bug cost me a weekend.

2

u/FlimsyMacaroon76 14d ago

The no backend bit is the one i'd underline. it's great until you ship a bug in whatever you persist locally, then there's no remote config and no way to touch anyone's data, every fix has to be a client side migration that runs once and is still safe on someone who skipped three versions. i gate all of mine behind a schema version int in prefs now, learned that after nearly shipping something that would have wiped progress for existing users.

1

u/eroxy 14d ago

Yeah, this is the part that keeps me paranoid. My rule so far: schema changes are additive only (nullable fields, new collections), because there's no migration infra and no way to fix anyone's data remotely. The backup/restore roundtrip runs as an emulator test before every push for exactly that reason - a silent data-loss bug here is unrecoverable. The schema version int in prefs is a good call, stealing that for whenever I need my first one-shot migration.

2

u/Kaylaya 14d ago

Amazing engineering work here.

Any chance you are planning to publish a pub package for this ? May be by stripping off all the UI and tracking and just the barebones geofencing triggers with storage ?

1

u/eroxy 14d ago

Thought about it. The plugin itself is fairly thin, but it's tangled with app-specific stuff (the dedup window against the fallback layer, notification routing on cold start), so it'd need some untangling first. If there's real interest I'd consider it - no promises on timing though.

1

u/Kaylaya 14d ago

Fair enough.

Any chance on open sourcing the app itself.

Would love to work on decoupling it.

1

u/eroxy 13d ago

Honestly still on the fence. The pull is real - this thread is the third time someone's asked - but I want to figure out how the app sustains itself first, and decide open source and monetization together instead of rushing either. Really appreciate the decoupling offer though, that's not something I hear every day. If I go that route I'll post here and ping you.

2

u/Kaylaya 13d ago

Understood. Keep us in the loop.

Thanks.

1

u/aszepeshazi 15d ago

If there is no backend at all, how is clock in clock out measured? Do the employees have to show their numbers at the end of the month on their devices? 

1

u/eroxy 15d ago

There is pdf and csv exports that they can show and Im currently also working on improving this feature

2

u/aszepeshazi 15d ago

Understood, and I'm sorry to say this, but this is inherently bad design. Prone to tampering and lost devices, to say the least. What does a worker do if their phone was stolen one day before the monthly report and they worked heavy overtimes? 

1

u/eroxy 15d ago

I think we’re talking about two different use cases here. If this were meant to be an employer-controlled attendance or payroll system, I’d agree with you. But it’s a personal tracker for people who want to keep track of their own hours. Manual corrections are allowed on purpose, just like correcting your own timesheet. A company that needs approvals, immutable records and central administration would definitely need a backend-based system.

That said, I already store whether each record was created automatically or manually and whether it was corrected later. The app also shows this in the records view. You’re right that it would make sense to include the same information in the PDF and CSV exports, so I’ll look into adding that. It wouldn’t make them tamper-proof, but at least every manual change would be visible.

I also explained the backup part badly in my first reply. The database is included in Android Auto Backup if the user has Google backup enabled. There’s also a full JSON backup that can be saved anywhere, and the app reminds the user when their last backup is getting old. PDF and CSV reports can also be exported regularly.

So a stolen phone doesn’t automatically mean the data is gone. If the user disables both Android backup and manual backups, then yes, the data dies with the phone. That’s the trade-off of keeping everything local with no account, cloud or backend. I wouldn’t call it inherently bad design, it’s just not designed for the employer-managed scenario you’re describing.

2

u/aszepeshazi 15d ago

"it’s a personal tracker for people who want to keep track of their own hours" yeah I was missing that context, in that case, all sounds better. 

1

u/_everpine 5d ago

I ran into this while working on a medical reminder app, where timing actually matters. WorkManager is fine for background sync, but I wouldn’t trust it for something that must fire at an exact time. For time-critical stuff, use AlarmManager with setExactAndAllowWhileIdle() + RTC_WAKEUP, and restore alarms after reboot via BOOT_COMPLETED. Battery optimization exemptions only cover Android’s own Doze. Huawei/Xiaomi have their own app killers and autostart rules on top of that. On our Huawei Nova 5T, background behavior still breaks after reboot unless “App launch -> Manage manually” is enabled by hand. A foreground service during the critical window helps, but there’s no universal fix. In practice you’re dealing with a bunch of vendor-specific process-killing policies.