DEV Community

Architecting for 100% Offline: Handling Android Geofencing Without Play Services

It was the second day of my cousin’s wedding. The ceremony was intimate, set in a quiet, traditional hall where silence wasn’t just requested-it was expected. Just as the proceedings reached a solemn moment, my pocket erupted with a high-pitched notification sound. I scrambled to silence the device, but the damage was done. The sharp, digital chirping cut through the stillness like a knife, drawing dozens of annoyed glares. I had forgotten to flip the silent switch again.

That precise moment of embarrassment was the final straw in a long history of notification-induced social friction. We all live this reality daily. Whether it is a crucial boardroom presentation, a university lecture, or a moment of personal prayer, our phones are designed to demand attention. The existing solutions were either too manual-relying on my unreliable memory-or tied to cloud-based services that required constant internet connectivity and invasive data collection.

I wanted a system that was truly autonomous. I didn't want to rely on a server to tell my phone it was in a meeting room, and I certainly didn't want to sync my private calendar data to a third-party cloud just to keep my phone muted. The friction wasn't just in the silencing; it was in the trade-off between privacy and automation. I needed an architecture that lived entirely on the device, one that would work in a basement with zero signal, respecting the user's data as a first-class citizen.

Abandoning Google Play Services Geofencing

To build this locally, I had to abandon the standard Google Play Services GeofencingClient approach. While the standard API is convenient, it is essentially a black box that relies on Google’s proprietary location backend, which can be inconsistent on non-GMS devices or in areas with poor GPS signals.

Instead, I opted for a custom implementation using the LocationManager API combined with a ForegroundService. By manually handling LocationRequest updates, I could define a circle radius around a coordinate and calculate the distance using the Haversine formula. This gave me granular control over the polling frequency, allowing me to strike a balance between battery drain and responsiveness.

private fun isInsideGeofence(current: Location, target: GeofenceRoutine): Boolean {
    val results = FloatArray(1)
    Location.distanceBetween(
        current.latitude,
        current.longitude,
        target.latitude,
        target.longitude,
        results
    )
    return results[0] <= target.radiusMeters
}

This architecture forced me to manage my own wake locks and location listeners. Because I wanted the app to survive reboots, I registered a BroadcastReceiver to catch ACTION_BOOT_COMPLETED. This triggers the initialization of the ForegroundService, which then resumes the monitoring cycle. By avoiding dependency on external location providers, I ensured that the logic remains strictly binary: if the math says you are inside the perimeter, the AudioManager executes the RINGER_MODE_SILENT command immediately. It is raw, it is predictable, and it does not require a single packet of data to be sent off-device to a remote server.

Dealing with GPS Jitter and Hardware Inconsistencies

What truly caught me off guard was the inconsistency of GPS hardware across different manufacturers. I initially assumed that a 100-meter radius would be perfectly stable. In reality, I found that on some budget devices, the GPS signal would 'drift' while the phone was stationary, causing the app to rapidly toggle the sound profile on and off as the location jumped in and out of my defined boundary. This created a 'jitter' effect that was far more disruptive than the original notification sound I was trying to block.

I had to implement a hysteresis buffer-essentially a time-based or distance-based 'cooldown'-to ensure that a user had to be solidly inside or outside the radius for a set duration before the state changed.

Background Location and Battery Optimization Surprises

Another assumption I got wrong was how Android handles background location. I thought that keeping a ForegroundService running would be enough to keep the location listener alive. I learned the hard way that aggressive battery optimization settings on phones from vendors like Xiaomi or Samsung would kill my process regardless of the notification I displayed.

I had to build a specific guide into the app to help users navigate to 'Don't Kill My App' settings, but more importantly, I had to architect a recovery mechanism. If the service died, the app now checks the last known location upon restart and re-evaluates all active routines immediately. If I were starting over, I would have prioritized a more robust state-persistence layer using Room from day one, rather than relying on shared preferences for routine statuses, which caused me significant headaches during unexpected crashes.

Key Takeaways for Offline-First Development

When building tools for personal automation, the temptation is always to add features that look good on a feature list, like cloud sync or social sharing. However, the most important lesson I learned is that privacy and reliability are features in their own right. By keeping everything local, I removed the complexity of authentication and API latency, which in turn made the app significantly more robust.

The takeaway for any developer is to embrace the 'offline-first' mindset not as a limitation, but as a design constraint that simplifies your state machine. When your app doesn't rely on the network, you eliminate an entire class of failure modes-timeout exceptions, null responses, and connectivity toggles. Always design for the edge case where the user has no signal. If your app only works when it can reach a server, it isn't really a tool; it is a service.

By focusing on the device's native sensors and local database, you build something that feels like an extension of the phone's operating system itself. That is the philosophy behind Muffle, a utility that manages sound profiles based on location, time, and prayer schedules without ever needing a connection. You can see how these offline-first principles come together in the implementation at https://play.google.com/store/apps/details?id=com.muffle.app.

Start small, control your sensor data, and prioritize the user's peace of mind above all else.

Comments

No comments yet. Start the discussion.