Performance8 min read
Flutter Geofencing Without Battery Drain
Most geofence features die from battery drain, not bugs. Five rules for battery-friendly geofencing in Flutter: radius, dwell, permissions, transitions.
- #flutter
- #geofencing
- #location
- #battery-optimization
- #background

Geofencing is one of those features that sells itself. Your app notices when the user walks into the office and switches their phone to silent, then restores the ringer when they leave. No taps, no remembering — the app just knows where it is.
I know this pattern well because it is the core of Smart Silent, my Android app that automatically switches phone sound profiles based on scheduled times or geographic location. Under the hood it is battery-efficient geofencing: location-based profile switching that keeps battery usage under 1.2 percent per 24 hours, backed by deep OS-level controls for calls, media, and notification volumes.
The reason most geofencing implementations get deleted is not accuracy — it is battery life. A geofence feature that keeps the GPS radio hot all day is a feature users switch off, and a switched-off feature might as well not exist. Here are the five engineering rules that make geofencing in Flutter reliable and cheap enough to leave on permanently.
How platform geofencing actually works
Both mobile operating systems already solved battery-efficient geofencing, and your job as a Flutter developer is mostly to not fight them.
On Android, the GeofencingClient registers geofences with the fused location provider. The OS batches location requests across every app on the device, so your 100-meter office geofence usually costs your app nothing extra — the radio wakes are shared with everything else running. Android supports up to 100 geofences per app and delivers three transition types: enter, exit, and dwell (the user has stayed inside for a configured time).
On iOS, region monitoring with circular regions works the same way at a conceptual level. The system can monitor your regions even when the app is suspended or terminated, waking it in the background to deliver enter and exit events. iOS limits an app to 20 simultaneously monitored regions, so iOS designs must register only the regions near the user and re-register as they move.
The single most important takeaway: never replace these native APIs with your own GPS polling loop. Polling the current position on a timer keeps the GPS radio awake and is the number one reason geofence features drain batteries.
Rule 1: Treat radius as a battery setting
Geofence radius is not just a UX decision — it is a power decision. Large geofences (100 meters and up) can be served by the fused location provider using low-power WiFi and cell-tower positioning. Small geofences (20 meters) force the system to wake the GPS chip to get fine fixes, which is dramatically more expensive.
As a rule of thumb, make 100 meters your minimum practical radius unless the use case truly demands otherwise. An office auto-mute feature does not need to know which floor the user is on — it needs to know they arrived at the office campus. If a product designer asks for a 15-meter geofence around a store entrance, push back and propose a larger zone with a dwell requirement instead. I explored how these power tradeoffs compound across a whole app in my guide on mobile battery optimization: https://www.uzairxdev.online/blog/graphql-vs-rest-mobile-battery
Rule 2: Debounce every transition
GPS positions drift. A phone sitting on a desk near the edge of a geofence will report positions that flicker in and out of the boundary, and naive code will fire enter, exit, enter, exit in a loop. This is not just annoying — every false transition wakes your app and costs power.
The fix is a small state machine with a dwell requirement: only fire a transition after the new state persists for a minimum duration. Here is a compact implementation using the geolocator package:
import 'package:geolocator/geolocator.dart';
/// Confirms geofence transitions only after the new state persists,
/// so GPS drift at the boundary does not cause enter/exit flapping.
class GeofenceMonitor {
final double latitude;
final double longitude;
final double radiusMeters;
final Duration dwellTime;
bool _inside = false;
DateTime? _crossedAt;
GeofenceMonitor({
required this.latitude,
required this.longitude,
this.radiusMeters = 150,
this.dwellTime = const Duration(minutes: 2),
});
/// Returns 'enter' or 'exit' on a confirmed transition, null otherwise.
String? onPosition(Position position) {
final distance = Geolocator.distanceBetween(
position.latitude,
position.longitude,
latitude,
longitude,
);
final nowInside = distance <= radiusMeters;
final now = DateTime.now();
if (nowInside == _inside) {
_crossedAt = null;
return null;
}
_crossedAt ??= now;
if (now.difference(_crossedAt!) < dwellTime) {
return null; // Still unconfirmed - wait out the drift.
}
_inside = nowInside;
_crossedAt = null;
return _inside ? 'enter' : 'exit';
}
}
Native geofence APIs have this concept built in — Android supports a loitering delay when registering a geofence, and iOS lets you require the device to be inside a region for a minimum time before notifying. Use the platform delay when registering the geofence, and keep a debounce layer in Dart for anything you compute manually.
Rule 3: Earn background permission in context
Geofencing only matters when the user is not looking at your app, which means you need background location access — the most suspicious permission an app can ask for. Getting this wrong is how geofence features ship and never get enabled.
On Android 10, background location ("Allow all the time") became a separate request from foreground location. On Android 11 and later, the system deliberately refuses to show it in a dialog — the user must grant it in Settings. That means your permission flow must first request foreground access, then explain exactly why the feature needs background access, and then deep-link the user to Settings. Requesting background permission in an onboarding flow, before the user has even tried the feature, will convert at close to zero.
On iOS, the flow is similar: request "While Using", then upgrade to "Always" only at the moment the user enables the geofence feature, with purpose strings that say what the feature does in plain language.
A minimal permission helper looks like this:
Future<bool> ensureGeofencePermission() async {
if (!await Geolocator.isLocationServiceEnabled()) {
return false; // Ask the user to enable location services.
}
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
}
if (permission == LocationPermission.deniedForever) {
// The only way out is Settings - deep-link there with an explanation.
await Geolocator.openAppSettings();
return false;
}
return permission == LocationPermission.always ||
permission == LocationPermission.whileInUse;
}
Remember: on Android, even after this succeeds with whileInUse, background geofencing still needs the separate "Allow all the time" grant, which you must guide the user to in Settings. Do not ask for it until the feature is in front of them.
Rule 4: React to transitions cheaply
A geofence transition handler should do the smallest possible work: flip a local flag, update a scheduled profile, post a notification. That is it. If you fire local notifications on transitions, make sure that path is lean too — my full notifications setup guide covers the efficient route: https://www.uzairxdev.online/blog/flutter-firebase-push-notifications-guide
What kills the battery budget is everything developers stack on top of the transition — a network sync on every enter and exit, a database compaction, a full data refresh that pulls from the server. In a place with spotty reception, a user crossing the boundary repeatedly can trigger dozens of sync attempts, each one keeping the cellular radio awake.
Instead, treat the transition as a signal and defer the heavy work. Update local state immediately, then let WorkManager on Android or a background refresh task on iOS pick up any network sync under its own power-efficient schedule, with exponential backoff if it fails. I cover this deferral pattern in more detail in my offline-first architecture guide: https://www.uzairxdev.online/blog/offline-first-architecture-crdt-mobile
Rule 5: Test with the platform, not against it
Geofences are hard to test by walking around the block, and you should not have to. The Android emulator's extended controls let you script location routes, and the iOS simulator accepts GPX files that simulate movement — including entering and leaving your geofence coordinates. Test the full path: registration, enter with dwell, exit, and what happens when location services are disabled mid-monitoring.
Then do one real-world test in a release build. Debug builds keep location services and logging far more active than production, so battery numbers measured in debug mode are meaningless. One afternoon walk with the release build will show you the real transition latency and confirm your dwell settings are sane.
What this looks like in a real app
Smart Silent puts all five rules together. It combines a scheduling system for recurring silent periods — meetings, sleep, prayer times — with battery-efficient geofencing for location-based profile switching, using deep OS-level controls to manage media, calls, and notification volumes accurately. When the event ends or the user leaves the area, profiles revert to normal automatically. The result is geofence accuracy you can rely on at under 1.2 percent battery cost per 24 hours.
If you are building anything location-aware, you can see how the app presents these features in the Smart Silent case study: https://www.uzairxdev.online/projects/smart-silent
Conclusion
Geofencing done right is boring in the best way: the OS does the heavy lifting, your radius is generous, transitions are debounced, permissions are earned in context, and handlers do almost nothing. The battery budget of a well-built geofence feature is close to zero because most of the cost is shared with location work the phone was already doing.
Get any one of these wrong and the feature still works in the demo — it just quietly becomes the reason users uninstall the app. Build for the battery, and the feature gets to keep existing.
Want help shipping location features that respect the battery? Get in touch and let's talk about your project.
Frequently asked questions
- Why does geofencing drain the battery in Flutter apps?
- It drains the battery when the app polls the GPS instead of using the platform's native geofence APIs. Keeping the GPS radio awake all day is what costs power, not the geofence concept itself.
- What is the best geofence radius for battery life?
- Keep radii at 100 meters or larger whenever the use case allows. Larger radii are served from low-power WiFi and cell-tower positioning, while small radii force continuous GPS fixes.
- How many geofences can an app monitor?
- Android supports up to 100 geofences per app through the GeofencingClient, while iOS limits an app to 20 simultaneously monitored regions.
- Do geofences work when the app is closed?
- Yes on both platforms. The OS monitors native geofences even when the app is suspended or terminated and wakes the app to deliver enter and exit transitions.


