It was the middle of a Friday sermon. The mosque was silent, the air heavy with focus, until a high-pitched, upbeat ringtone tore through the stillness. It wasn't mine, but the collective wince from the hundred people around me was visceral. I watched the man frantically scramble to pull his phone from his pocket, his face bright red with embarrassment. It is a universal human experience: the crushing weight of being the person who interrupted a moment of peace, simply because we forgot to flip a physical switch.
That moment was the spark for Muffle. I realized that the friction wasn't just about silence; it was about the cognitive load of remembering to change settings in a world where our lives are increasingly governed by location and schedule. We have all been there—the meeting that starts abruptly, the class where the professor has a zero-tolerance policy, or the appointment where you just want to be present. Existing solutions were either too manual, requiring me to open an app and tap a button every time I walked into a building, or they were bloated, battery-hungry monsters that tracked my location with such high precision that my phone died by 3 PM.
I wanted something that just worked, completely in the background, without turning my device into a space heater. The problem with most location-based apps is their reliance on the LocationManager with high-accuracy GPS requests. If you poll for coordinates continuously, you are essentially asking the system to keep the GPS radio active, which is a death sentence for battery life. Android provides the GeofencingClient API, which is built on top of Google Play Services, specifically to solve this. Instead of the app constantly asking 'Where am I?', the system handles the heavy lifting, batching location updates and only waking the app when a predefined geofence boundary is crossed.
Implementing this effectively meant shifting my mental model from 'tracking' to 'event-based monitoring'. I defined circular geofences with a radius of 100 meters, which is the sweet spot for avoiding false positives while still being accurate enough for a building or campus. The real challenge, however, is handling the PendingIntent that triggers when the user enters or exits that area. I had to ensure that the broadcast receiver was lean. If you perform heavy operations—like database writes or network calls—inside the onReceive method of a BroadcastReceiver, the system will kill the process to save memory.
kotlin
val geofencingRequest = GeofencingRequest.Builder().apply {
setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
addGeofences(geofenceList)
}.build()
val geofencePendingIntent = PendingIntent.getBroadcast(
context, 0, Intent(context, GeofenceBroadcastReceiver::class.java),
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
)
I structured the GeofenceBroadcastReceiver to simply hand off the work to a WorkManager task. This decouples the location event from the sound profile logic. By using WorkManager, I ensure that even if the system is under memory pressure, the task is queued and executed reliably without blocking the main thread or causing an ANR. The GeofencingClient itself operates at the system level, meaning it consumes almost zero battery when the phone is stationary. It only wakes up when the fused location provider detects a transition that matches the geofence.
What surprised me most during development was the 'edge case of proximity'. I initially set my geofences to 50 meters, assuming that tighter was better for accuracy. I was wrong. I found that in dense urban environments with poor GPS signal or signal reflection off tall buildings, the device would oscillate between 'inside' and 'outside' states, creating a 'flapping' effect. The phone would vibrate, go silent, then unmute, then go silent again in a span of three minutes. This is maddening for a user and creates a terrible experience. I had to implement a debounce mechanism. I now use a buffer zone and a small time delay before the sound profile actually switches, preventing these rapid-fire toggles from triggering unnecessary sound changes.
Another assumption I had to discard was that the system would always reliably trigger the exit event. In my early testing, I found that if the user turned their phone off or killed the app process entirely, the geofence sometimes failed to fire the exit signal, leaving the phone in 'Silent' mode forever. To fix this, I had to architect a state-recovery system. Every time the app starts, it queries the current location against the active geofences in the database. If a user is currently inside a defined area, the app forces a state sync. This 'reboot-safety' was harder to implement than the actual geofencing itself, because it required me to maintain a local persistent store of the last known state, independent of the volatile system-level geofence state.
If I were to start over, I would focus even more heavily on the user’s battery statistics from day one. I spent weeks optimizing the code only to realize that the most common cause of drain wasn't the GPS at all—it was the ForegroundService I was using to keep the app alive. I was holding a wake lock far longer than necessary. I moved to a more granular approach, using AlarmManager for time-based triggers and only keeping the GeofencingClient active for relevant geofences. The lesson here is that as an Android developer, you are a steward of the user’s resources. Every CPU cycle you consume is a choice you make against their battery life.
For anyone building location-aware features, my biggest takeaway is this: trust the system APIs over your own polling logic. Google spent years refining how location services interact with the battery, and if you try to build your own loop, you will almost certainly fail to account for the weird ways Android handles background processes. Treat your geofences as zones that the system 'manages' for you rather than data you are 'collecting'. Focus on the edge cases—the reboots, the signal drops, and the flapping states—rather than just the 'happy path' where everything works perfectly. That is where the actual engineering happens. If you are interested in how I managed these triggers for things like prayer times or calendar events, you can see how Muffle handles these rules locally at https://play.google.com/store/apps/details?id=com.muffle.app
Top comments (0)