<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Haseeb</title>
    <description>The latest articles on DEV Community by Haseeb (@haseebthedev0).</description>
    <link>https://dev.to/haseebthedev0</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3995506%2F6e1f9d2c-8016-47aa-972a-8060903ed6a9.webp</url>
      <title>DEV Community: Haseeb</title>
      <link>https://dev.to/haseebthedev0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/haseebthedev0"/>
    <language>en</language>
    <item>
      <title>Building Reliable Geofencing Without a Backend Dependency</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Thu, 16 Jul 2026 00:37:04 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/building-reliable-geofencing-without-a-backend-dependency-24cp</link>
      <guid>https://dev.to/haseebthedev0/building-reliable-geofencing-without-a-backend-dependency-24cp</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon prayer at the mosque. The imam had just begun the recitation when a phone in the third row started blaring a custom ringtone at maximum volume. The person fumbled, dropped it, and in their haste to silence it, accidentally hit the volume rocker until it was silent—but they forgot to turn it back up afterward. I sat there watching the scene unfold, realizing that the standard Android volume controls were entirely reactive, not proactive. My own device was in my pocket, and I wondered if I could build something that just handled this for me without needing a cloud database or constant internet access.&lt;/p&gt;

&lt;p&gt;We live in an era where almost every mobile application demands a persistent network connection. If the servers go down, or if the user is in a basement with poor reception, the functionality vanishes. But when you are building a tool designed to manage physical sound profiles—something that should be as fundamental as the hardware itself—relying on a network call to verify a location or a routine is a massive architectural failure. The friction lies in the latency between the intent and the execution. If my phone needs to ping a server to check if I am at the office before it silences itself, and that server request hangs due to a weak signal, the phone rings anyway. That is not just an inconvenience; it is a point of social failure that users shouldn't have to navigate.&lt;/p&gt;

&lt;p&gt;To build Muffle, I decided early on that the application had to be entirely offline. This meant moving all logic, including complex prayer time calculations and geofencing triggers, directly onto the user's device. I chose the &lt;code&gt;GeofencingClient&lt;/code&gt; from the Google Play Services library as my primary tool, but the real challenge wasn't just drawing a circle on a map; it was managing the transitions without a persistent background socket. I had to ensure that the &lt;code&gt;IntentService&lt;/code&gt; or &lt;code&gt;BroadcastReceiver&lt;/code&gt; would wake up accurately, even if the device was in deep Doze mode, and process the transition before the user walked through the door. I opted to use a combination of &lt;code&gt;AlarmManager&lt;/code&gt; for precise time-based triggers and &lt;code&gt;GeofencingClient&lt;/code&gt; for spatial triggers. The decision to use &lt;code&gt;PendingIntent&lt;/code&gt; for the geofence transitions was critical; it allows the system to wake up the app, execute the necessary &lt;code&gt;AudioManager&lt;/code&gt; call to toggle the ringer mode, and then immediately return to a sleep state, conserving battery while maintaining high reliability.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofenceRequest = GeofencingRequest.Builder().apply {&lt;br&gt;
    setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)&lt;br&gt;
    addGeences(geofenceList)&lt;br&gt;
}.build()&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(geofenceRequest, geofencePendingIntent)&lt;br&gt;
    .addOnSuccessListener { /* Geofence added &lt;em&gt;/ }&lt;br&gt;
    .addOnFailureListener { /&lt;/em&gt; Handle error */ }&lt;/p&gt;

&lt;p&gt;By keeping this logic local, I avoided the pitfalls of latency. When the device crosses the geofence, the system fires the intent directly to my app's broadcast receiver, which triggers the &lt;code&gt;AudioManager.RINGER_MODE_SILENT&lt;/code&gt; command. No network round-trip, no DNS lookup, no failed API calls. It is just the OS talking to the local application logic. This architecture ensures that the sound profile updates happen within milliseconds of the user entering the predefined zone, which is the only way to make an automation app feel like a native, reliable part of the operating system.&lt;/p&gt;

&lt;p&gt;What truly surprised me during development was the volatility of the &lt;code&gt;FusedLocationProviderClient&lt;/code&gt;. I initially assumed that if I set a geofence radius of 50 meters, the OS would trigger precisely at that boundary. I was wrong. Android’s location accuracy varies wildly depending on whether the user is indoors, relying on Wi-Fi scanning, or outdoors with a clear view of GPS satellites. My first iteration failed constantly because it relied too heavily on high-accuracy GPS, which would often drift when the user moved into a large building. I learned that for a tool like Muffle, a 50-meter radius is actually dangerous. If the device's signal drifts, the geofence doesn't trigger until the user is already deep inside the room. I had to increase the minimum radius to 150 meters and implement a logic layer that checks for 'dwell time' to avoid constant toggling if the user is hovering near the boundary line. &lt;/p&gt;

&lt;p&gt;Another assumption I had to discard was the idea that 'silent' meant 'silent.' I discovered that many users had legitimate emergency contacts—like family members or childcare providers—who needed to reach them regardless of the profile. Adding an emergency bypass feature wasn't just a quality-of-life add-on; it was a fundamental necessity. I had to integrate with the &lt;code&gt;NotificationManager.Policy&lt;/code&gt; to properly handle 'Priority' calls, which was a nightmare of documentation reading and trial-and-error testing across different Android versions. If I were starting over, I would have spent more time building a robust testing harness for &lt;code&gt;AudioManager&lt;/code&gt; changes across various API levels from the start, rather than relying on my own device as the primary test bench. The differences in how Android 12, 13, and 14 handle Do Not Disturb permissions are subtle but can break the entire app if you aren't strictly checking &lt;code&gt;isNotificationPolicyAccessGranted&lt;/code&gt; before attempting to change the phone's state.&lt;/p&gt;

&lt;p&gt;For any developer building automation, the biggest lesson is to respect the platform's constraints rather than fighting them. Do not try to keep a foreground service running indefinitely if you can accomplish the same task with &lt;code&gt;AlarmManager&lt;/code&gt; and &lt;code&gt;BroadcastReceivers&lt;/code&gt;. The more you rely on the system's own scheduling tools, the more stable your app will be when the OS decides to aggressively kill background processes. If you are handling sensitive user data—like location or calendar access—keep it local. Users are far more likely to trust an app that declares it has no internet permissions than one that requires a login, a password, and a cloud sync feature just to silence a ringer. Keeping the logic offline is not just a technical challenge; it is a design philosophy that prioritizes user privacy and system stability above all else.&lt;/p&gt;

&lt;p&gt;In my work on Muffle, I have found that the most effective tools are those that vanish into the background once they are set up. If you are interested in how I handled these local-only triggers, you can see how I implemented the geofencing and prayer time logic in the project at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. Building for the edge of the device requires a shift in mindset, but it results in a much cleaner, more responsive user experience.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Architecting a fully offline Android sound manager with Room</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Tue, 14 Jul 2026 21:30:17 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-a-fully-offline-android-sound-manager-with-room-9mc</link>
      <guid>https://dev.to/haseebthedev0/architecting-a-fully-offline-android-sound-manager-with-room-9mc</guid>
      <description>&lt;p&gt;It happened during a lecture. I was sitting in the front row, pen poised, when my phone erupted with a loud, brassy notification sound. It wasn't just a ping; it was a long, jarring melody that echoed off the auditorium walls. Heads turned. The professor paused mid-sentence, glaring at me over his spectacles. I fumbled to silence the device, but in my panic, I accidentally toggled the volume instead of silencing it. My face burned as I realized I had completely forgotten to adjust my settings before walking into the room.&lt;/p&gt;

&lt;p&gt;That recurring sense of dread is something most of us know. Whether it is a quiet library, a high-stakes meeting, or a moment of personal reflection, the human element of remembering to toggle phone settings is remarkably unreliable. I found myself constantly checking my volume rocker or double-checking my calendar, yet I still slipped up. I wanted a way for my phone to manage its own silence without needing a cloud connection or an account that tracked my location data. I wanted a local, persistent system that just worked.&lt;/p&gt;

&lt;p&gt;When I started building Muffle, I decided early on that this application would be completely offline. Privacy is often treated as an afterthought in modern development, but for an app that knows your location and your schedule, it should be the foundation. I chose Room as my persistence layer because it offers a clean abstraction over SQLite while keeping the data strictly on the device. My architecture relies on a Room database acting as the single source of truth for all &lt;code&gt;Routine&lt;/code&gt; entities. Every trigger—whether it is a time-based schedule, a geofenced area, or a calendar event—is serialized and stored locally.&lt;/p&gt;

&lt;p&gt;The core challenge was ensuring that the app could react to these triggers without maintaining a persistent, battery-draining connection to a backend server. I utilized &lt;code&gt;WorkManager&lt;/code&gt; for background tasks, which allows the app to schedule work that is guaranteed to execute even if the app is closed or the device reboots. When a &lt;code&gt;Routine&lt;/code&gt; is created, the app inserts the logic into the Room database and schedules a corresponding &lt;code&gt;OneTimeWorkRequest&lt;/code&gt; or &lt;code&gt;PeriodicWorkRequest&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
&lt;a class="mentioned-user" href="https://dev.to/entity"&gt;@entity&lt;/a&gt;(tableName = "routines")&lt;br&gt;
data class Routine(&lt;br&gt;
    @PrimaryKey(autoGenerate = true) val id: Int = 0,&lt;br&gt;
    val triggerType: String,&lt;br&gt;
    val soundAction: String,&lt;br&gt;
    val isActive: Boolean = true,&lt;br&gt;
    val startTime: Long? = null&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;By keeping the &lt;code&gt;Room&lt;/code&gt; database as the source of truth, I can query the current state of any routine at any time. If the user edits a routine, the &lt;code&gt;WorkManager&lt;/code&gt; job is cancelled and a new one is queued with the updated parameters. This decoupled approach prevents race conditions and ensures that the system handles device restarts gracefully. Upon boot, the app registers a &lt;code&gt;BroadcastReceiver&lt;/code&gt; that triggers a sync task, re-validating the database and re-scheduling the necessary background workers to restore the user's previous state without manual intervention.&lt;/p&gt;

&lt;p&gt;What surprised me most was the complexity of the &lt;code&gt;GeofencingClient&lt;/code&gt;. I initially assumed that simply setting a radius around a coordinate would be enough, but reality is far messier. GPS signal drift is a genuine problem, especially in urban environments with tall buildings. I spent weeks fighting scenarios where the geofence would trigger prematurely or fail to trigger entirely because the device's location accuracy fluctuated. I had to implement a small buffer zone and a cooldown period for the triggers, preventing the system from flapping between 'silent' and 'normal' if the user stood exactly on the edge of a location boundary.&lt;/p&gt;

&lt;p&gt;Another realization was how much the &lt;code&gt;AudioManager&lt;/code&gt; API expects the developer to be a good citizen. If I just set the mode to &lt;code&gt;RINGER_MODE_SILENT&lt;/code&gt;, I might inadvertently block critical alerts that the user actually wants. I had to learn the nuances of &lt;code&gt;NotificationManager.Policy&lt;/code&gt; to correctly implement the 'Do Not Disturb' exception list. Trying to map these system-level settings to a user-friendly UI without exposing the underlying mess of Android’s &lt;code&gt;Notification&lt;/code&gt; permissions was a lesson in humility. If I were starting over, I would have invested more time in creating a comprehensive mock-testing suite for the &lt;code&gt;WorkManager&lt;/code&gt; chains. Debugging real-time background events is notoriously difficult; being able to simulate the passing of time or the triggering of a geofence in a virtual environment would have saved me hundreds of hours of manual testing.&lt;/p&gt;

&lt;p&gt;Building for offline persistence forced me to think more deeply about data integrity. When you don't have a server to validate data, your local database schema must be robust enough to handle conflicts on its own. For anyone building a productivity tool, my advice is to prioritize the local database structure before writing a single line of UI code. If your data model is weak, no amount of frontend polish will compensate for the eventual bugs in state management. Think about how your app behaves when the user loses internet—or better yet, design it to never need the internet at all.&lt;/p&gt;

&lt;p&gt;Developing for Android requires acknowledging that your app is just one of many processes fighting for resources. By keeping Muffle lean and relying on native system APIs like &lt;code&gt;WorkManager&lt;/code&gt; and &lt;code&gt;Room&lt;/code&gt;, I managed to create a solution that feels like an extension of the operating system rather than a battery-draining parasite. You can see how these pieces come together in the actual implementation at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;, where I continue to iterate on these local-first principles to ensure the app remains as lightweight and functional as possible.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>androiddev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Battery-Friendly Geofencing: Lessons from Building Muffle</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Mon, 13 Jul 2026 23:42:18 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/battery-friendly-geofencing-lessons-from-building-muffle-omf</link>
      <guid>https://dev.to/haseebthedev0/battery-friendly-geofencing-lessons-from-building-muffle-omf</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon at the community library. I was deep into a debugging session for a client project when my phone erupted with a loud, brassy ringtone. Heads turned, glares were exchanged, and the librarian gave me a look that could freeze liquid nitrogen. I scrambled to silence the device, but the damage was done. I had missed a crucial meeting, and worse, I had disrupted everyone around me. That moment of pure, unadulterated social friction stayed with me long after I packed my laptop bag.&lt;/p&gt;

&lt;p&gt;We all have those moments. Maybe it is during a, a final exam, a medical consultation, or a quiet prayer. The issue is that human memory is fundamentally flawed. We intend to silence our phones, but we get distracted by the last-minute details of our day. Existing solutions were either too manual—requiring me to toggle settings every single time—or they were so heavy on system resources that they drained my battery by lunchtime. I wanted a set-and-forget experience. I wanted a way to define a boundary on a map and have my phone respect that boundary without me needing to touch a single button. I needed an automation tool that actually understood the context of my environment.&lt;/p&gt;

&lt;p&gt;When I started building Muffle, I knew geofencing was the backbone of the experience. The immediate challenge was the Android battery optimization landscape. If I implemented a simple &lt;code&gt;LocationListener&lt;/code&gt; to poll the GPS coordinates every few seconds, I would effectively be creating a battery-destroying engine that users would uninstall within hours. The Android system is aggressive about killing background processes, and for good reason. I had to move away from active polling and embrace the &lt;code&gt;GeofencingClient&lt;/code&gt; API. This API offloads the heavy lifting to the Google Play Services layer, which is far more efficient at hardware-level batching than any user-space service I could write.&lt;/p&gt;

&lt;p&gt;However, the tradeoff is that &lt;code&gt;GeofencingClient&lt;/code&gt; uses a pending intent system. You register a set of circular regions, and the system notifies your app only when a transition—entering or exiting—occurs. The challenge then becomes maintaining the state correctly after the phone restarts. I had to implement a custom &lt;code&gt;BroadcastReceiver&lt;/code&gt; that listens for &lt;code&gt;ACTION_BOOT_COMPLETED&lt;/code&gt; to re-register these transitions. Without this, the geofences would simply vanish into the ether the moment the device rebooted, leaving the user unprotected. I also had to balance the &lt;code&gt;LoiteringDelay&lt;/code&gt; parameter. If you set it too low, you get false positives from GPS drift; if you set it too high, the phone remains unmuted for minutes after you have already left the location. Below is a snippet of how I define the request to ensure efficiency:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofence = Geofence.Builder()&lt;br&gt;
    .setRequestId("office_zone")&lt;br&gt;
    .setCircularRegion(lat, lng, 100f)&lt;br&gt;
    .setExpirationDuration(Geofence.NEVER_EXPIRE)&lt;br&gt;
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)&lt;br&gt;
    .setLoiteringDelay(30000) // 30 seconds to prevent flickering&lt;br&gt;
    .build()&lt;/p&gt;

&lt;p&gt;This setup allows the OS to handle the signal processing, only waking up my app when a boundary is truly crossed. It keeps the battery impact negligible, as the hardware sensor hub does the bulk of the work, and the application layer stays in a suspended state until needed.&lt;/p&gt;

&lt;p&gt;The most surprising lesson I learned during development was that the GPS hardware is rarely the primary source of battery drain—it is actually the context switching and the network requests associated with resolving location metadata. I originally assumed that if I just kept the geofencing radius wide, I would save battery. I was wrong. By setting the radius too wide, I created "ghost transitions" where the phone would enter a silent state while I was still driving past my office on a highway. The inaccuracy forced me to implement a secondary verification check: checking the &lt;code&gt;ConnectivityManager&lt;/code&gt; to ensure the location shift was legitimate, or at least filtering out transitions that occurred while the device was moving at high speeds.&lt;/p&gt;

&lt;p&gt;Another edge case that caught me off guard was the way Android handles "Doze Mode." When the phone is stationary for a long period, even the &lt;code&gt;GeofencingClient&lt;/code&gt; gets throttled. I had to accept that there is a latency period between arriving at a location and the phone actually silencing. I spent weeks trying to minimize this, but eventually realized that attempting to force near-zero latency would lead to the system killing my process for excessive resource consumption. I chose to prioritize system stability over instantaneous switching. This meant building a UI that gives the user immediate feedback that a routine is active, even if the hardware hasn't fully triggered yet, which keeps the user from manually overriding the app and causing conflicts.&lt;/p&gt;

&lt;p&gt;If I were starting over, I would handle the transition between GPS and Wi-Fi-based location more gracefully. I relied heavily on GPS, but in dense urban environments with tall buildings, GPS signal bounce is a real problem. Integrating a simple proximity check using Wi-Fi SSID detection would have likely improved the user experience in indoor environments where GPS signals struggle to penetrate. It is a common pitfall to assume the GPS is the only tool in your belt; in reality, a hybrid approach using available network signals is often more reliable for indoor geofencing.&lt;/p&gt;

&lt;p&gt;For any developer working on automation tools, the biggest takeaway is to respect the platform boundaries. It is tempting to write a custom service that polls for everything because it gives you control, but that control comes at the cost of the user's trust. If your app is the reason someone's phone dies before the end of the day, they will not care how many features you have or how well your geofencing logic works. Always prefer the system-managed APIs over custom implementations. Let the OS manage the power states, and focus your energy on the logic that happens &lt;em&gt;after&lt;/em&gt; the trigger fires. The complexity should live in how you handle the user's intent, not in how you track their location.&lt;/p&gt;

&lt;p&gt;Building tools that handle sensitive functions like audio profiles requires a focus on reliability and transparency. Users need to know exactly why their phone just went silent. I implemented a simple activity log in Muffle so that when the phone does silence, the user can instantly see that it was because of a specific routine trigger. This removes the mystery and builds confidence in the automation. If you are building similar utilities, keep the logs visible and the logic auditable. It makes for a much better user experience when the technology works in the background without feeling like a black box. You can see how I approached these constraints and the full implementation of these routines at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;&lt;/p&gt;

</description>
      <category>android</category>
      <category>androiddev</category>
      <category>kotlin</category>
      <category>programming</category>
    </item>
    <item>
      <title>Architecting reliable geofencing in Android without burning battery</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Mon, 13 Jul 2026 01:20:19 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-reliable-geofencing-in-android-without-burning-battery-5b2n</link>
      <guid>https://dev.to/haseebthedev0/architecting-reliable-geofencing-in-android-without-burning-battery-5b2n</guid>
      <description>&lt;h2&gt;
  
  
  Opening hook
&lt;/h2&gt;

&lt;p&gt;The silence in the lecture hall was heavy, the kind that only exists right before a professor delivers a final exam prompt. I was sitting in the third row, mentally reviewing my notes, when my phone decided it was the perfect moment to announce a incoming call with a high-pitched, insistent ringtone. Every head turned. The professor paused, sighed, and waited for me to scramble. I fumbled with the volume rocker, face burning red, feeling that specific, sinking frustration of having failed a simple, repetitive task that I should have automated months ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;That moment wasn't unique to me, and it certainly wasn't the first time it happened. We live in an era of constant connectivity, but our devices are surprisingly bad at understanding context. I needed a way to ensure my phone wouldn't disturb my environment during prayer, classes, or meetings, but the native Android 'Do Not Disturb' settings were too binary. They were either on or off, requiring manual intervention every single time I changed locations or entered a scheduled event. &lt;/p&gt;

&lt;p&gt;I looked at existing solutions, but they were either bloated with telemetry, required invasive permissions, or simply failed to respect the device's sleep cycles. I wanted something that functioned as a background utility—a silent partner that managed sound profiles based on location or time without draining the battery. The friction wasn't just in the manual toggling; it was in the cognitive load of having to remember to flip a switch before walking into a room. I realized that if I wanted a tool that actually respected my privacy and my battery life, I had to stop looking for apps and start writing the logic myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical decision / implementation
&lt;/h2&gt;

&lt;p&gt;When I started building Muffle, my initial instinct was to poll the device location every few minutes using a &lt;code&gt;LocationManager&lt;/code&gt; update loop. I quickly realized that this was a recipe for disaster. Polling the GPS radio is the fastest way to kill a battery, and it rarely provides the precision needed for geofencing. Instead, I shifted my architecture to use the &lt;code&gt;GeofencingClient&lt;/code&gt; within the Google Play Services library. This API is designed specifically to handle boundary-crossing events at the system level, allowing the OS to batch these checks and wake up the app only when necessary.&lt;/p&gt;

&lt;p&gt;However, relying on the &lt;code&gt;GeofencingClient&lt;/code&gt; isn't a silver bullet. The challenge is handling the transition events reliably, especially when the device is in Doze mode. I implemented a &lt;code&gt;BroadcastReceiver&lt;/code&gt; that triggers an &lt;code&gt;IntentService&lt;/code&gt; (and later, a &lt;code&gt;JobIntentService&lt;/code&gt; to maintain compatibility with modern Android API levels) to handle the &lt;code&gt;AudioManager&lt;/code&gt; state changes. The key here was ensuring the transition happens even if the app is process-killed.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofencingRequest = GeofencingRequest.Builder().apply {&lt;br&gt;
    setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)&lt;br&gt;
    addGeofence(geofence)&lt;br&gt;
}.build()&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)&lt;br&gt;
    .addOnSuccessListener { /* Geofence added &lt;em&gt;/ }&lt;br&gt;
    .addOnFailureListener { /&lt;/em&gt; Handle failure */ }&lt;/p&gt;

&lt;p&gt;By leveraging the &lt;code&gt;Geofence&lt;/code&gt; object's &lt;code&gt;setLoiteringDelay&lt;/code&gt;, I prevented the sound profile from flickering if the user was just walking past the boundary. I also had to explicitly handle the &lt;code&gt;ACTION_BOOT_COMPLETED&lt;/code&gt; intent. If the phone reboots, the &lt;code&gt;GeofencingClient&lt;/code&gt; doesn't automatically persist all state in the way you might expect unless you re-register the geofences. I built a boot-receiver that re-initializes the geofencing service upon startup, ensuring that the 'mute' state persists across power cycles. This approach keeps the heavy lifting in the OS kernel rather than my own code, which is the only way to maintain background reliability without triggering the battery-optimization alerts that Android's modern architecture is so aggressive about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised you / what you'd do differently
&lt;/h2&gt;

&lt;p&gt;The most humbling part of this project was discovering how inconsistent GPS hardware is across manufacturers. I spent a week debugging why my geofences wouldn't trigger on a specific mid-range device, only to find that the OEM's custom battery management was aggressively killing my &lt;code&gt;PendingIntent&lt;/code&gt; before it could even execute the &lt;code&gt;AudioManager&lt;/code&gt; change. I initially assumed that if I followed the documentation, the system would treat my service as a priority. I was wrong.&lt;/p&gt;

&lt;p&gt;I also learned that Geofencing is not a substitute for high-precision location. If I were starting over, I would build a multi-layered verification system. Relying solely on the &lt;code&gt;GeofencingClient&lt;/code&gt; is fine for large areas, but for small, tight boundaries like a specific office suite, it struggles with drift. I would have implemented a secondary check: if the user triggers a geofence, I would perform a low-power network-based location check to confirm they are actually inside the building before muting the phone. This would prevent the 'false positives' that occur when you walk near the edge of a geofence but don't actually enter. Additionally, I would have spent less time worrying about the &lt;code&gt;AudioManager&lt;/code&gt; and more time focusing on the &lt;code&gt;NotificationManager&lt;/code&gt; policies. Modern Android (API 23+) has a very specific way of handling 'Do Not Disturb' access that requires a deep understanding of &lt;code&gt;NotificationManager.Policy&lt;/code&gt;. I spent too many hours fighting the system because I didn't realize that standard &lt;code&gt;AudioManager&lt;/code&gt; calls were being overridden by restrictive DND settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;p&gt;If there is one thing I want you to take away from this, it is to stop fighting the Android OS and start playing by its rules. Every time I tried to force a background task to run continuously, the OS fought back, killed my process, and drained the user's battery. The secret to a stable background app isn't forcing it to stay awake; it is writing it so that it can sleep and be woken up by the system exactly when it needs to be. Whether you are using &lt;code&gt;GeofencingClient&lt;/code&gt;, &lt;code&gt;WorkManager&lt;/code&gt;, or &lt;code&gt;AlarmManager&lt;/code&gt;, you must design for the 'event-driven' nature of Android. &lt;/p&gt;

&lt;p&gt;Always assume your app will be killed by the system. If you aren't saving your state to a local database (like Room) and re-registering your triggers after a boot event, your app will eventually stop working for your users. Automation is powerful, but only if it's reliable enough to be forgotten. If you want to see how I've handled these edge cases in production, you can check out Muffle at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. It's a work in progress, just like all our code, but it's a testament to the fact that with enough persistence, you can turn a annoying technical limitation into a stable, useful utility for thousands of people.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>androiddev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Architecting Location-Based Automation Without Killing the Battery</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Sun, 12 Jul 2026 02:54:11 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-location-based-automation-without-killing-the-battery-362i</link>
      <guid>https://dev.to/haseebthedev0/architecting-location-based-automation-without-killing-the-battery-362i</guid>
      <description>&lt;h2&gt;
  
  
  Opening hook
&lt;/h2&gt;

&lt;p&gt;It happened during a quiet afternoon in the library. I was deep in a documentation sprint, and the only sound was the rhythmic tapping of my mechanical keyboard. Suddenly, my phone erupted into a high-pitched, aggressive ringtone that seemed to echo off every wall. Every head in the room turned toward me in unison. My face burned as I scrambled to silence the device, fumbling with the volume buttons while the caller—a telemarketer, of all people—continued to interrupt the silence. It was a humiliating, avoidable moment of pure friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We live in an age where our phones are supposedly "smart," yet they consistently fail at the most basic context-aware tasks. I found myself constantly needing to switch my phone to silent or vibrate, but the human error component was 100 percent. I would enter a meeting, forget to silence, and pray I didn’t get a call. I would leave a prayer or a lecture, forget to unmute, and then miss urgent calls for the rest of the afternoon. &lt;/p&gt;

&lt;p&gt;Existing solutions felt heavy-handed. Many automation apps relied on massive, bloated frameworks that kept the CPU awake, draining my battery just to check if I was near a specific building. I didn't want a system that required constant polling or cloud-based synchronization just to realize I was at work or at the gym. I needed something that felt native, lightweight, and, above all, respectful of the hardware's limited power budget. I wanted a way to define boundaries where my phone would simply handle itself, without me having to remember a single toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical decision / implementation
&lt;/h2&gt;

&lt;p&gt;When I started building Muffle, the biggest challenge was the Geofencing API. The temptation is to use &lt;code&gt;LocationManager&lt;/code&gt; and track the device's coordinates in real-time, but that’s an immediate death sentence for battery life. Instead, I opted for the &lt;code&gt;GeofencingClient&lt;/code&gt; within the Google Play Services library. This is a crucial distinction: &lt;code&gt;LocationManager&lt;/code&gt; gives you raw data that you have to process, whereas the &lt;code&gt;GeofencingClient&lt;/code&gt; offloads the heavy lifting to the OS. The OS uses hardware-level batching to handle location triggers, which is significantly more efficient than manual polling.&lt;/p&gt;

&lt;p&gt;To ensure the app survives reboots and doesn't get killed by aggressive background management, I implemented a &lt;code&gt;ForegroundService&lt;/code&gt;. This service runs a &lt;code&gt;BroadcastReceiver&lt;/code&gt; that listens for the &lt;code&gt;ACTION_BOOT_COMPLETED&lt;/code&gt; intent. When the phone restarts, I re-register the geofences with the &lt;code&gt;GeofencingClient&lt;/code&gt; immediately. The logic for the transition looks like this:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofencingRequest = GeofencingRequest.Builder().apply {&lt;br&gt;
    setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)&lt;br&gt;
    addGeofences(geofenceList)&lt;br&gt;
}.build()&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(geofencingRequest, geofencePendingIntent)&lt;br&gt;
    .addOnSuccessListener { /* Successfully registered &lt;em&gt;/ }&lt;br&gt;
    .addOnFailureListener { /&lt;/em&gt; Handle errors */ }&lt;/p&gt;

&lt;p&gt;The key here is the &lt;code&gt;PendingIntent&lt;/code&gt;. By using a &lt;code&gt;PendingIntent&lt;/code&gt;, I don't need my app’s process to be alive to catch the location event. The Android system broadcasts the intent directly to my receiver, which then triggers the &lt;code&gt;AudioManager&lt;/code&gt; to change the sound profile. This architecture means Muffle spends 99 percent of its time doing absolutely nothing, waiting for a hardware-level interrupt. It only wakes up when the user crosses the designated boundary. This separation of the registration logic from the execution logic was the single most important decision I made to keep the resource footprint minimal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised you / what you'd do differently
&lt;/h2&gt;

&lt;p&gt;I initially assumed that the accuracy of the GPS would be the biggest hurdle. I spent weeks worrying about "GPS drift," where the device might falsely trigger the silent profile while I was just walking past a building. I spent hours tuning the &lt;code&gt;loiteringDelay&lt;/code&gt; parameter, thinking that tighter radius constraints were the answer. I was wrong.&lt;/p&gt;

&lt;p&gt;What actually destroyed my first iteration was indoor signal degradation. In large, concrete buildings, the GPS signal often drops or jumps wildly, which caused the system to think I had left and re-entered a zone multiple times within minutes. My code was spamming the &lt;code&gt;AudioManager&lt;/code&gt; with redundant requests, which triggered notification sounds (in some ROMs) or even briefly un-silenced the phone during the transition. The non-obvious fix wasn't about the GPS at all; it was about state management. I had to implement a debounce buffer. Even if the geofence triggered, the app now checks if the sound profile is already in the requested state before issuing a command to the system. &lt;/p&gt;

&lt;p&gt;If I were starting over, I would prioritize Wi-Fi based triggers as a secondary verification layer. Relying solely on GPS is fine for outdoors, but for a truly "smart" experience, combining GPS coordinates with Wi-Fi SSID detection provides a much higher degree of confidence. Relying on a single sensor is a common trap for beginners; real-world environments are far messier than the emulator in Android Studio suggests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;p&gt;If you are building an app that interacts with system state or hardware sensors, stop looking for the most "powerful" way to do it and start looking for the most "lazy" way. The Android OS is designed to handle background tasks efficiently if you play by its rules—specifically by offloading work to system services rather than writing your own polling loops. Always use &lt;code&gt;PendingIntent&lt;/code&gt; for background events to let the OS wake your app only when necessary. &lt;/p&gt;

&lt;p&gt;Furthermore, never assume your code is the only thing running on the device. Your app is a guest in the user's ecosystem. If you drain the battery, you will be uninstalled, regardless of how useful your features are. Focus on state management and redundancy to account for the chaotic nature of physical movement. If you want to see how these pieces come together in a production-ready, privacy-focused context, you can check out my project, Muffle, at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. It’s a practical exercise in keeping things simple while solving a real, annoying problem that we’ve all faced at one point or another.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Architecting a background-controlled sound manager that actually survives Doze mode</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Fri, 10 Jul 2026 23:24:19 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-a-background-controlled-sound-manager-that-actually-survives-doze-mode-505m</link>
      <guid>https://dev.to/haseebthedev0/architecting-a-background-controlled-sound-manager-that-actually-survives-doze-mode-505m</guid>
      <description>&lt;p&gt;It happened during a lecture. I was sitting in the front row, taking notes on my tablet, when my phone started vibrating against the wooden desk. The sound was amplified like a drum. The professor paused, the entire room turned, and I scrambled to find the volume button. It was one of those moments where you wish you could just disappear into the floorboards. I had meant to silence it before entering, but the thought had completely slipped my mind during the rush of finding a seat. That was the exact moment I realized I had to automate this.&lt;/p&gt;

&lt;p&gt;Most of us live in a state of constant, low-level anxiety regarding our phone's sound profile. We attend meetings, medical appointments, or religious services where a sudden notification chime is the height of social friction. The current standard approach—manually toggling the ringer—is fundamentally flawed because it relies on human memory, which is notoriously unreliable in high-pressure or transitionary moments. We need a system that treats 'silence' as a context-aware state rather than a manual switch. Existing solutions often fail because they are either too complex for the average user or they get killed by the operating system the moment the screen turns off.&lt;/p&gt;

&lt;p&gt;When I started building Muffle, I assumed I could just fire a &lt;code&gt;BroadcastReceiver&lt;/code&gt; or use a simple &lt;code&gt;Handler&lt;/code&gt; to monitor triggers. I was wrong. Android’s aggressive battery optimization, specifically Doze mode and App Standby, is the enemy of any service attempting to maintain a consistent state. To build something that actually works, I had to move away from standard background execution and embrace a &lt;code&gt;Foreground Service&lt;/code&gt; combined with &lt;code&gt;AlarmManager&lt;/code&gt; for precise scheduling. The core challenge was ensuring that when a routine triggers—like entering a geofenced area or hitting a specific time—the &lt;code&gt;AudioManager&lt;/code&gt; actually processes the request even if the device has been sitting in a pocket for three hours.&lt;/p&gt;

&lt;p&gt;I settled on a architecture where the &lt;code&gt;Foreground Service&lt;/code&gt; acts as the primary controller, while &lt;code&gt;AlarmManager&lt;/code&gt; with &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt; handles the wake-up calls. This ensures that even if the system tries to put the app into a deep sleep, the OS is forced to wake it up at the precise moment a routine should trigger. Here is a snippet of how I handle the &lt;code&gt;AudioManager&lt;/code&gt; state transition within the service:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager&lt;br&gt;
if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.M) {&lt;br&gt;
    audioManager.ringerMode = AudioManager.RINGER_MODE_SILENT&lt;br&gt;
} else {&lt;br&gt;
    audioManager.ringerMode = AudioManager.RINGER_MODE_SILENT&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;However, changing the ringer mode is only half the battle. If a user has a 'Do Not Disturb' policy enabled, the &lt;code&gt;AudioManager&lt;/code&gt; might be restricted from making changes unless the app has the appropriate &lt;code&gt;NotificationPolicyAccess&lt;/code&gt; permission. I had to implement a check to prompt the user to grant access to the &lt;code&gt;DND&lt;/code&gt; settings during the initial setup. Without this specific permission, the &lt;code&gt;AudioManager&lt;/code&gt; calls fail silently, leaving the user wondering why their phone is still ringing during their scheduled meetings. It is a classic case of an API that works perfectly in a debug environment but requires specific, non-obvious user intervention in the wild.&lt;/p&gt;

&lt;p&gt;What truly surprised me during the development process was the behavior of GPS-based triggers. I initially thought that using the standard &lt;code&gt;LocationManager&lt;/code&gt; would be sufficient for geofencing. I quickly found out that requesting location updates while the phone is in a deep sleep state causes the GPS radio to drain the battery at an unacceptable rate, which in turn causes the Android system to 'punish' the app by revoking its background privileges entirely. I had to pivot to using the &lt;code&gt;GeofencingClient&lt;/code&gt; from the Google Play Services library. By shifting the heavy lifting of location monitoring to the system-level Google Play Services process, I could wait for the OS to broadcast a transition event rather than polling for coordinates myself. This is the difference between a battery-draining app and one that respects the device's resources.&lt;/p&gt;

&lt;p&gt;Another point of failure was the reboot process. I naively assumed that simply registering my receivers in the &lt;code&gt;AndroidManifest&lt;/code&gt; would be enough to handle device restarts. I missed the fact that if a user moves their app to an SD card or if the device is encrypted, the &lt;code&gt;BOOT_COMPLETED&lt;/code&gt; intent might not be received as expected. I had to add a persistent storage layer—using &lt;code&gt;Room&lt;/code&gt; database—to store the state of every routine. On startup, the service queries the database to see which routines should be currently active based on the current system time and the last known location. This 'state reconstruction' logic is what allows the app to feel like it never stopped running, even after a full system reboot.&lt;/p&gt;

&lt;p&gt;If I were to rebuild this today, I would invest much more time into the dependency injection layer using Hilt from day one. I started with a more manual approach to service instantiation, and it became a nightmare when trying to unit test the transition between 'Silent' and 'Normal' modes. Testing sound state changes requires mocking the &lt;code&gt;AudioManager&lt;/code&gt; and the &lt;code&gt;NotificationManager&lt;/code&gt;, which is significantly easier when you have a clean DI graph. I also underestimated the complexity of time zone changes. If a user travels to a different time zone, their scheduled routines can drift by hours if you rely on system-provided time strings rather than &lt;code&gt;ZonedDateTime&lt;/code&gt; objects. I had to refactor all my time calculations to use the &lt;code&gt;java.time&lt;/code&gt; API to ensure that a 9:00 AM meeting remains a 9:00 AM meeting regardless of where the user is located.&lt;/p&gt;

&lt;p&gt;For anyone looking to build background-heavy applications on Android, the biggest lesson is to stop fighting the OS and start working within its constraints. Don't try to keep a service running 24/7 if you can use &lt;code&gt;WorkManager&lt;/code&gt; for periodic tasks or &lt;code&gt;AlarmManager&lt;/code&gt; for precise ones. The goal is to be the 'best guest' on the user's device. If your app is responsible for the battery dropping 10% overnight, it doesn't matter how useful your features are; the user will uninstall it. Look into the 'Power Management' documentation for your specific target SDK version and test your app on a real device with battery optimization enabled. It is often the only way to catch the race conditions that occur when the system forces your process into the background.&lt;/p&gt;

&lt;p&gt;Building tools that solve these small, daily frictions is incredibly rewarding because you are solving a problem you personally experience. Muffle exists because I was tired of being the person whose phone rang during a lecture. It doesn't rely on complex server-side logic; it is a locally-driven tool that respects user privacy and device battery life. You can see how I approached these challenges in the implementation here: &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. Keep building, keep testing on real hardware, and don't be afraid to scrap your initial architecture if it doesn't survive a night of Doze mode.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>androiddev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Architecting Location Based Automation without Killing Android Battery Life</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Thu, 09 Jul 2026 21:48:16 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-location-based-automation-without-killing-android-battery-life-398e</link>
      <guid>https://dev.to/haseebthedev0/architecting-location-based-automation-without-killing-android-battery-life-398e</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon at the library. I was deep into a debugging session when my phone suddenly blared a loud, upbeat notification ringtone. Every head in the room turned toward me. My face burned as I scrambled to silence the device, fumbling with the volume rockers while my heart raced. I had forgotten to mute my phone after a morning meeting, and that simple oversight turned a productive study session into an uncomfortable public spectacle. I knew right then that manual sound management was a broken process for me.&lt;/p&gt;

&lt;p&gt;We have all been there. You walk into a lecture, a medical appointment, or a place of worship, and the inevitable happens. You either forget to toggle your sound profile, or you remember to silence it but fail to unmute it later, missing important calls from family or work. The friction lies in the human reliance on memory for repetitive, state-based tasks. Most existing solutions either force you to remember to click a button, or they rely on overly complex automation platforms that feel like overkill for a simple task like shifting from 'Vibrate' to 'Normal.' I needed a solution that just worked in the background, reliably and invisibly.&lt;/p&gt;

&lt;p&gt;When I started building Muffle, my primary goal was location-based automation. I wanted my phone to recognize when I entered specific zones—like my office or the mosque—and adjust the audio state automatically. The architectural challenge, however, is that Android is notoriously hostile to background processes that constantly poll location data. Using standard GPS updates in a background service is a recipe for a dead battery within three hours. I had to decide between high-accuracy polling and battery longevity. I chose the &lt;code&gt;GeofencingClient&lt;/code&gt; API, which leverages the Google Play Services location provider to handle the heavy lifting of boundary monitoring.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GeofencingClient&lt;/code&gt; is effective because it allows the operating system to batch location requests and use a combination of cell towers and Wi-Fi signals to determine proximity rather than relying solely on the power-hungry GPS radio. By registering a &lt;code&gt;PendingIntent&lt;/code&gt; with the API, I could offload the monitoring responsibility to the system process. When the user enters or exits a defined radius, the system wakes up my app only when necessary. This is significantly more efficient than running a &lt;code&gt;Service&lt;/code&gt; that loops a &lt;code&gt;LocationManager&lt;/code&gt; update every few seconds. Below is a snippet of how I register these triggers:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofence = Geofence.Builder()&lt;br&gt;
    .setRequestId(locationId)&lt;br&gt;
    .setCircularRegion(lat, lng, radius)&lt;br&gt;
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)&lt;br&gt;
    .setExpirationDuration(Geofence.NEVER_EXPIRE)&lt;br&gt;
    .build()&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(request, pendingIntent)&lt;br&gt;
    .addOnSuccessListener { /* Handle success &lt;em&gt;/ }&lt;br&gt;
    .addOnFailureListener { /&lt;/em&gt; Handle failure */ }&lt;/p&gt;

&lt;p&gt;This approach effectively separates the 'watching' logic from the 'acting' logic. The system handles the spatial math, and I only receive a broadcast when a transition occurs. This keeps the CPU idle for 99% of the day, which is the only way to ensure the app doesn't show up in the 'Battery Usage' list as a culprit. Integrating this with the &lt;code&gt;AudioManager&lt;/code&gt; to toggle between 'Silent,' 'Vibrate,' and 'Do Not Disturb' states allowed me to fulfill the core promise of the app without draining the user's hardware.&lt;/p&gt;

&lt;p&gt;What truly surprised me during development was the fragility of the &lt;code&gt;GeofencingClient&lt;/code&gt; when it comes to low-end hardware. I assumed that if I followed the documentation, the transitions would be near-instant. In reality, on many devices in my target markets, OEMs have aggressive 'battery optimization' settings that kill off the background listeners without warning. I spent nearly two weeks debugging why my geofences wouldn't trigger on certain older handsets. It turns out that the system would occasionally put my app into a 'restricted' state simply because it hadn't been opened in the foreground for a while, even if it had a valid foreground service running.&lt;/p&gt;

&lt;p&gt;I learned that relying solely on the system's geofencing callback is insufficient for a polished experience. I had to implement a 'resurrection' mechanism. By using a &lt;code&gt;WorkManager&lt;/code&gt; task that checks the status of my active routines every few hours, I can ensure that if the system kills my background listener, the app re-registers the geofences. I also had to account for 'location drift.' In areas with poor GPS coverage, the phone might report that you have exited a geofence when you haven't actually moved, due to a sudden shift in the cell tower triangulation. I had to add a small 'buffer zone' to my radius calculations to prevent the phone from rapidly switching sound profiles back and forth as the location signal wavered.&lt;/p&gt;

&lt;p&gt;If I were starting over, I would move away from relying on the standard geofencing triggers for everything. Instead, I would implement a hybrid approach where I use Wi-Fi SSID detection as a secondary verification layer. If the phone is connected to a specific known Wi-Fi network, I can treat that as a 'hard' location trigger, which is significantly more reliable and energy-efficient than GPS or cell-tower-based geofencing. It would have saved me a massive amount of headache regarding the accuracy of transitions in indoor environments.&lt;/p&gt;

&lt;p&gt;For any developer working on background tasks, the most important takeaway is that the Android system is not a static environment—it is a constantly shifting landscape of power-management policies. Do not assume your process will stay alive. Design your app to be stateless and robust enough to reconstruct its own monitoring state from a local database whenever it is woken up. Whether you are building a productivity tool, a fitness tracker, or a location-based utility, your architecture must assume failure as the default state. Always treat the background as a guest, not a resident.&lt;/p&gt;

&lt;p&gt;Automation should feel like an extension of your own intent, not an unpredictable guest. I built Muffle to bridge that gap between our devices and our actual lives, ensuring that we can attend to what matters without being interrupted by our own technology. If you are interested in seeing how this is implemented in practice or want to manage your own sound profiles without the manual effort, you can find the project here: &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. It has been a rewarding journey of balancing user experience with the strict realities of modern mobile hardware limitations.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Engineering background geofencing without killing the battery</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Wed, 08 Jul 2026 23:28:19 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/engineering-background-geofencing-without-killing-the-battery-13bd</link>
      <guid>https://dev.to/haseebthedev0/engineering-background-geofencing-without-killing-the-battery-13bd</guid>
      <description>&lt;h2&gt;
  
  
  Opening hook
&lt;/h2&gt;

&lt;p&gt;The silence in the lecture hall was absolute, the kind that feels heavy. The professor was mid-sentence, explaining a complex derivation on the whiteboard, when my pocket started buzzing. It wasn't just a vibration; it was a rhythmic, aggressive thumping against my thigh. I had forgotten to flip the silent switch after my morning gym session. As the ringing tone pierced the quiet, I scrambled to silence it, my face burning with embarrassment. It was the third time that month. I realized then that my reliance on manual toggles was a failure point I couldn't ignore.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We live in a world where our phones are expected to be context-aware, yet we still perform the mundane task of toggling sound profiles manually dozens of times a day. We mute for meetings, unmute for lunch, mute for prayer, and then repeat. The friction here is not just the act of tapping a button; it is the cognitive load of remembering to change settings in the first place. When you miss that mental cue, the consequence is social friction or personal disruption.&lt;/p&gt;

&lt;p&gt;Existing solutions often felt like overkill or were tied to intrusive cloud services. Many automation apps I tested relied on constant GPS polling, which turned my battery into a furnace within three hours. Others required active internet connections, which was a non-starter for my privacy-first approach. I wanted something that functioned entirely locally, remained dormant until absolutely necessary, and survived the aggressive memory management policies of modern Android versions. I wanted a background service that was invisible, not just in its UI, but in its impact on the hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical decision / implementation
&lt;/h2&gt;

&lt;p&gt;To solve this, I leaned heavily on the &lt;code&gt;GeofencingClient&lt;/code&gt; API rather than rolling my own location tracking. Many developers mistakenly believe they need to monitor &lt;code&gt;LocationManager&lt;/code&gt; and calculate distances in a continuous loop to detect entries and exits. That is a battery death sentence. By using the &lt;code&gt;GeofencingClient&lt;/code&gt;, I offload the heavy lifting to the Google Play Services layer, which is already optimized for batching location requests at the hardware level.&lt;/p&gt;

&lt;p&gt;The architectural challenge was ensuring the &lt;code&gt;IntentService&lt;/code&gt; or &lt;code&gt;BroadcastReceiver&lt;/code&gt; would trigger reliably even when the device was in Doze mode. I registered a &lt;code&gt;PendingIntent&lt;/code&gt; that broadcasts to a &lt;code&gt;BroadcastReceiver&lt;/code&gt;. This receiver is the entry point for my logic. Crucially, I had to ensure that the logic within the receiver was lightweight. If the processing takes too long, the system will kill the process to save resources.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofencingRequest = GeofencingRequest.Builder()&lt;br&gt;
    .addGeofence(geofence)&lt;br&gt;
    .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)&lt;br&gt;
    .build()&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(geofenceRequest, pendingIntent)&lt;br&gt;
    .run {&lt;br&gt;
        addOnSuccessListener { /* Geofence added &lt;em&gt;/ }&lt;br&gt;
        addOnFailureListener { /&lt;/em&gt; Handle registration error */ }&lt;br&gt;
    }&lt;/p&gt;

&lt;p&gt;I implemented a priority-based queue for sound states. If a location-based routine triggers an 'Enter' event, it pushes a state onto a local stack. When the 'Exit' event triggers, it pops that state and restores the previous one, or reverts to 'Normal' if no other conditions are active. This prevents the common bug where exiting one geofence accidentally defaults the phone to 'Normal' volume even if the user is still inside a separate, overlapping silent zone. By treating sound profiles as a stack of overrides rather than binary toggles, I created a system that respects overlapping context.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised you / what you'd do differently
&lt;/h2&gt;

&lt;p&gt;I initially assumed that the biggest challenge would be the precision of the GPS geofencing. I spent weeks tweaking radius sizes, worried that the phone would trigger too late or too early. It turns out, that was the easy part. The real nightmare was the 'Background Execution Limits' introduced in recent Android versions. I found that if my app was not in the foreground, the system would periodically strip my permissions or freeze my background service if it detected high battery usage.&lt;/p&gt;

&lt;p&gt;What truly shocked me was the inconsistency of the &lt;code&gt;AudioManager&lt;/code&gt; behavior across different manufacturers. On stock Android, &lt;code&gt;AudioManager.RINGER_MODE_SILENT&lt;/code&gt; works exactly as documented. On certain heavily skinned devices from Asian markets, the OS would aggressively override my sound changes because its own 'Phone Manager' software thought my app was behaving suspiciously by changing system settings without user interaction. I had to implement a retry-loop with a backoff strategy that specifically checks if the ringer mode was successfully applied, and if not, logs the failure for the user to troubleshoot.&lt;/p&gt;

&lt;p&gt;If I were starting over, I would move away from relying on &lt;code&gt;BroadcastReceiver&lt;/code&gt; for state restoration. Instead, I would use &lt;code&gt;WorkManager&lt;/code&gt; for almost everything. &lt;code&gt;WorkManager&lt;/code&gt; is much better at guaranteeing execution even if the app is force-closed or the device reboots. My initial reliance on standard alarms meant that a phone reboot would wipe my schedule until the app was manually opened again. Integrating &lt;code&gt;WorkManager&lt;/code&gt; with a &lt;code&gt;BOOT_COMPLETED&lt;/code&gt; receiver was the necessary fix that I should have prioritized on day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;p&gt;If you are building an app that interacts with hardware sensors or system settings, stop trying to reinvent the wheel by keeping a service alive 24/7. Modern Android is designed to kill your process. Instead, shift your architecture toward event-driven triggers. Use &lt;code&gt;GeofencingClient&lt;/code&gt; for location, &lt;code&gt;AlarmManager&lt;/code&gt; for time, and &lt;code&gt;WorkManager&lt;/code&gt; for state persistence. Your goal is to write code that only exists for the few milliseconds it takes to trigger an action.&lt;/p&gt;

&lt;p&gt;Always design for the edge case where the system shuts you down. If your application state isn't written to a local database—I use Room for this—then you are building a fragile system that will fail the moment the user restarts their phone. By focusing on local, event-driven architecture, I was able to build Muffle as a lightweight tool that respects the user's battery life. You can see how this all comes together at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;, where I continue to refine these background routines to be as unobtrusive as possible.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Architecting persistent background tasks against Android Doze mode</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Tue, 07 Jul 2026 22:17:08 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-persistent-background-tasks-against-android-doze-mode-1ngm</link>
      <guid>https://dev.to/haseebthedev0/architecting-persistent-background-tasks-against-android-doze-mode-1ngm</guid>
      <description>&lt;h2&gt;
  
  
  The Silent Crisis
&lt;/h2&gt;

&lt;p&gt;I was sitting in a quiet, sunlit conference room during a high-stakes client presentation. The air was still, and every minor sound seemed amplified tenfold. Suddenly, the silence shattered. My phone, tucked away in my pocket, decided that was the perfect moment to blast a loud, generic ringtone. Everyone turned. I scrambled to silence it, but the damage was done. It wasn't just a missed mute toggle; it was a recurring failure of memory. I realized then that relying on manual input for something as context-dependent as sound control was a losing battle for any human.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Friction of Manual Control
&lt;/h2&gt;

&lt;p&gt;We live in a world of constant notification noise, but we also inhabit spaces that demand silence. Whether it is a place of worship, a lecture hall, or a medical clinic, the requirement to toggle sound profiles is ubiquitous. Most people rely on the native Quick Settings shade, but the human element is the primary point of failure. We forget. We arrive late, we rush in, or we are simply distracted. &lt;/p&gt;

&lt;p&gt;I looked for existing solutions to automate this, but I found either bloated suites that required constant internet connectivity or overly simplistic apps that failed to respect the nuances of modern Android power management. The problem isn't just about turning the volume to zero; it is about the reliability of the trigger. If your GPS geofence fails to fire because the OS killed your process to save battery, or if your calendar sync misses an event update, the automation is worse than useless—it is misleading. I wanted a system that was truly local, privacy-centric, and, above all, capable of surviving the aggressive background process restrictions that define the Android ecosystem today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrestling with the Foreground Service
&lt;/h2&gt;

&lt;p&gt;To build Muffle, I had to architect a system that could handle multiple, competing trigger types—GPS, time, calendars, and prayer schedules—without draining the battery or getting evicted by the system's &lt;code&gt;JobScheduler&lt;/code&gt;. The core of my implementation relies on a long-running &lt;code&gt;ForegroundService&lt;/code&gt;. On modern Android, specifically from Android 8.0 (Oreo) and beyond, simply running a background task is no longer an option. If you want to perform reliable sound modifications based on geofencing or time-sensitive calculations, you must maintain a persistent notification to the user.&lt;/p&gt;

&lt;p&gt;However, keeping a service alive is only half the battle. I opted to use the &lt;code&gt;AlarmManager&lt;/code&gt; for time-based triggers, specifically &lt;code&gt;setExactAndAllowWhileIdle&lt;/code&gt;. This is crucial because standard alarms are deferred during Doze mode to save power. By using the &lt;code&gt;Window&lt;/code&gt; parameter, I ensure that even when the device is in a low-power state, the system wakes the app to process the state change. Here is a snippet of how I handle the scheduling logic:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager&lt;br&gt;
val intent = Intent(context, RoutineReceiver::class.java)&lt;br&gt;
val pendingIntent = PendingIntent.getBroadcast(context, routineId, intent, PendingIntent.FLAG_IMMUTABLE)&lt;/p&gt;

&lt;p&gt;alarmManager.setExactAndAllowWhileIdle(&lt;br&gt;
    AlarmManager.RTC_WAKEUP,&lt;br&gt;
    triggerTimeMillis,&lt;br&gt;
    pendingIntent&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;For geofencing, I leaned into the &lt;code&gt;GeofencingClient&lt;/code&gt; API. The mistake many developers make is trying to poll location data themselves, which is a massive battery drain and a quick way to be flagged by the system as a resource hog. By offloading the geofence monitoring to the OS level, I let the hardware handle the proximity calculations. When the boundary is crossed, the OS wakes my &lt;code&gt;BroadcastReceiver&lt;/code&gt;, which then triggers the sound state update. This keeps the application footprint minimal while ensuring that the transition from 'Normal' to 'Silent' is immediate, regardless of whether the screen is currently locked or the app is sitting in a background state.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lessons of Unexpected Failure
&lt;/h2&gt;

&lt;p&gt;I initially assumed that keeping an app in the background would be simple if I just followed the documentation for Foreground Services. I was wrong. The biggest hurdle was the 'Samsung factor' and other OEM-specific battery optimizations. I discovered that even if I implemented everything according to the AOSP standards, devices from companies like Xiaomi or Samsung would aggressively kill my process if the user didn't manually whitelist the app. This was a brutal realization; no matter how clean my code was, the OS-level 'Battery Saver' was constantly working against my logic.&lt;/p&gt;

&lt;p&gt;Another surprise was the complexity of the Prayer Time calculation. I integrated a standard library to handle the astronomical calculations, but I found that users in different regions have wildly different expectations for 'Asr' timing based on different calculation methods (like Hanafi vs. Shafi'i). I had to pivot from a hard-coded approach to a highly configurable settings module that allowed users to adjust the time offsets manually. If I were starting over, I would have spent much more time on the 'conflict resolution' logic. Initially, I thought a simple stack-based approach for conflicting routines would work. But when a user has a Calendar event overlapping with a GPS geofence, the state flailed. I eventually had to build a 'Priority Matrix' that calculates the highest-weighted sound state every time a change is triggered, ensuring the phone doesn't toggle vibrate on and off repeatedly in a jittery loop. It taught me that in automation, predictability is more important than raw speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Designing for Resilience
&lt;/h2&gt;

&lt;p&gt;Building for Android means accepting that you are a guest on the user's device. You are not entitled to resources. The most important takeaway for any developer working on background-heavy applications is to stop fighting the system and start working with its constraints. Do not try to keep a service 'alive' by force if the OS wants to kill it; instead, design your application to be stateless enough that it can be destroyed and recreated at any time without losing the user's context.&lt;/p&gt;

&lt;p&gt;Always persist your trigger states in a local database like Room. If your service gets killed, the first thing your &lt;code&gt;BootReceiver&lt;/code&gt; should do upon restart is query the database and re-evaluate the current state. If you rely on memory-resident variables, you will inevitably lose the sound profile state when the phone reboots or the system reclaims memory. By treating the state as a single source of truth in a local database, you ensure that the user's phone remains silent or active exactly as requested, even after a system crash. This is the philosophy I used to build Muffle, which you can explore at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. Reliability in automation is not about having a perfect process that never stops; it is about having a system that always knows how to resume exactly where it left off.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobiledev</category>
      <category>androiddev</category>
    </item>
    <item>
      <title>Architecting a location-aware sound manager without killing battery</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Mon, 06 Jul 2026 21:57:11 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-a-location-aware-sound-manager-without-killing-battery-3c4h</link>
      <guid>https://dev.to/haseebthedev0/architecting-a-location-aware-sound-manager-without-killing-battery-3c4h</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon at the library. I was deep into a debugging session for a client project when my phone decided it was the perfect moment to blast a ringtone at maximum volume. The entire room turned, faces filled with irritation. I fumbled to silence it, but in the panic, I hit the volume rocker too many times and put it on silent, missing an important call later that evening. That moment of public embarrassment was the final straw. I realized my phone, which is supposed to be a tool, was actively sabotaging my focus and social standing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;We all live in a constant state of toggling. We mute our phones before a meeting, remember to unmute them afterward, then inevitably forget to silence them again for a prayer, a lecture, or a medical appointment. Android provides manual controls, but the human element is the weak link. I wanted something that didn't require me to check a settings menu five times a day. I needed a system that understood context—where I was and what time it was—and adjusted the hardware state accordingly. The existing solutions were either too heavy, requiring constant GPS polling that decimated my battery, or too simple, lacking the logic to handle overlapping rules or emergency bypasses. I wanted a set-and-forget experience that felt like part of the OS, not a resource-heavy burden running in the background. My goal was to build a tool that respected both my schedule and my device’s energy efficiency, ensuring the silent mode was there when I needed it, and gone when I didn't, without me ever touching a button.&lt;/p&gt;

&lt;h2&gt;
  
  
  The technical decision / implementation
&lt;/h2&gt;

&lt;p&gt;To build Muffle, I had to solve the classic Android paradox: how to stay location-aware without a constant &lt;code&gt;Location&lt;/code&gt; service draining the battery. I initially experimented with &lt;code&gt;LocationManager&lt;/code&gt; and the &lt;code&gt;GPS_PROVIDER&lt;/code&gt;, but that was a mistake. It forces the hardware to stay active, leading to significant battery drain. Instead, I pivoted to the &lt;code&gt;GeofencingClient&lt;/code&gt; within the Google Play Services library. This API is much more efficient because it offloads the monitoring to the system's location subsystem, which uses a mix of cellular towers and Wi-Fi access points rather than just raw GPS.&lt;/p&gt;

&lt;p&gt;Even with &lt;code&gt;GeofencingClient&lt;/code&gt;, the issue of triggering sound profiles remains. I needed a way to manage the transition between &lt;code&gt;AudioManager&lt;/code&gt; states—like &lt;code&gt;RINGER_MODE_SILENT&lt;/code&gt;, &lt;code&gt;RINGER_MODE_VIBRATE&lt;/code&gt;, and &lt;code&gt;RINGER_MODE_NORMAL&lt;/code&gt;—while respecting the user’s overrides. I implemented a custom &lt;code&gt;ForegroundService&lt;/code&gt; to act as the central brain. By using a Foreground Service with a persistent notification, I ensured the OS wouldn't kill my process during memory pressure, which is critical for a background automation app. The core logic uses a high-priority queue to manage conflicting routines:&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager&lt;br&gt;
if (shouldMute) {&lt;br&gt;
    audioManager.ringerMode = AudioManager.RINGER_MODE_SILENT&lt;br&gt;
} else {&lt;br&gt;
    audioManager.ringerMode = AudioManager.RINGER_MODE_NORMAL&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;However, simply switching the ringer mode wasn't enough. I had to integrate &lt;code&gt;NotificationManager.INTERRUPTION_FILTER_ALL&lt;/code&gt; versus &lt;code&gt;INTERRUPTION_FILTER_PRIORITY&lt;/code&gt; to allow for "emergency bypass" contacts. By checking the user's whitelist against the incoming &lt;code&gt;Bundle&lt;/code&gt; extras in my broadcast receiver, I could allow specific calls to ring through even when the system was set to Do Not Disturb. This hybrid approach—using the system's geofencing hardware for location and an internal priority queue for sound state—allowed me to keep battery consumption minimal while maintaining strict adherence to the user’s preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised you / what you'd do differently
&lt;/h2&gt;

&lt;p&gt;What truly caught me off guard was how inconsistently manufacturers handle the &lt;code&gt;AudioManager&lt;/code&gt; API. I assumed that a call to &lt;code&gt;setRingerMode&lt;/code&gt; would be universal. I was wrong. Some OEMs, particularly those with aggressive battery optimization layers, would occasionally intercept the call or delay the execution of the state change if the phone was in a deep sleep state. I spent two weeks debugging why my phone wouldn't silence at my office location, only to realize that the &lt;code&gt;BroadcastReceiver&lt;/code&gt; responsible for triggering the action was being throttled by the OS's power management policy.&lt;/p&gt;

&lt;p&gt;To fix this, I had to move from simple broadcast receivers to a more robust &lt;code&gt;WorkManager&lt;/code&gt; implementation for routine scheduling. &lt;code&gt;WorkManager&lt;/code&gt; is much more "polite" to the system, but it also guarantees execution. If I were starting over, I would have avoided trying to build a custom "state engine" from scratch for the first version. I spent too much time trying to manage conflicting rules manually. I should have implemented a simple SQL-backed priority table from day one, allowing the database to be the source of truth for which routine wins in a conflict. Relying on volatile memory for routine state led to bugs where an active routine would simply 'forget' itself after a system reboot. Building a persistent storage layer early on would have saved me hundreds of hours of debugging inconsistent states and edge-case failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical takeaway
&lt;/h2&gt;

&lt;p&gt;If you are building an app that relies on background triggers, the biggest lesson I learned is to never trust the OS to keep your process alive indefinitely. Always assume the user's phone will kill your background tasks to save battery, and architect for that reality. Use &lt;code&gt;WorkManager&lt;/code&gt; for tasks that need to happen reliably, and use the system’s built-in APIs like &lt;code&gt;GeofencingClient&lt;/code&gt; instead of trying to roll your own location listeners. Don't fight the Android ecosystem; work with the constraints it provides. If you need to manage sound or system settings, check the &lt;code&gt;NotificationManager&lt;/code&gt; policies early and often, because user settings in those menus will often override your app's commands without throwing an error. By focusing on the user’s intent rather than just the code execution, you can build tools that feel native and reliable. I’ve tried to incorporate these lessons into my own utility, Muffle, which handles these exact logic flows for automating sound profiles based on location or time. You can see how I approached these problems in the source-adjacent logic at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>androiddev</category>
      <category>mobiledev</category>
    </item>
    <item>
      <title>Architecting a Battery-Efficient Geofencing Service for Android 14</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Mon, 06 Jul 2026 01:58:19 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-a-battery-efficient-geofencing-service-for-android-14-53m7</link>
      <guid>https://dev.to/haseebthedev0/architecting-a-battery-efficient-geofencing-service-for-android-14-53m7</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon in the mosque. The imam was midway through a soft, reflective portion of the sermon when a sharp, melodic ringtone cut through the silence like a knife. Every head turned. The person whose phone it was scrambled to silence it, visibly flustered, but the damage was done. The atmosphere of concentration was shattered. I sat there with my own phone in my pocket, perfectly silent, because I had spent the last three weeks obsessing over why my previous automation attempts kept failing or draining my battery to zero by noon.&lt;/p&gt;

&lt;p&gt;We have all been there. Whether it is a lecture hall, a medical appointment, or a board meeting, the social friction caused by a sudden, avoidable noise is universal. Existing solutions often fall into two camps: they are either battery-hungry monsters that keep the GPS radio pinned at all times, or they are inconsistent, failing to trigger the moment you step into the room. I wanted something that felt like it wasn't even there—a system that sat quietly in the background, consuming practically zero CPU cycles, and yet fired the exact second I crossed a physical threshold. Achieving this on modern Android, with its increasingly restrictive background execution policies, turned into a masterclass in resource management.&lt;/p&gt;

&lt;p&gt;My initial approach was naive. I thought I could simply register a &lt;code&gt;LocationListener&lt;/code&gt; and check the distance against my target coordinates inside a &lt;code&gt;Service&lt;/code&gt;. I quickly realized that this is the fastest way to get a "Battery usage too high" warning from the OS. In Android 14, keeping the GPS radio active is a death sentence for any app's retention rate. If the system sees your app holding a wake lock or polling for location while the screen is off, it will kill your process without hesitation. I had to shift my entire mental model from "active polling" to "event-driven passive listening."&lt;/p&gt;

&lt;p&gt;I pivoted to the &lt;code&gt;GeofencingClient&lt;/code&gt; API, which is part of Google Play Services. Instead of managing the GPS hardware myself, I offload the heavy lifting to the system. The system maintains a low-power geofencing engine that only wakes up my application when a specific transition (entering or exiting) occurs. The architecture is straightforward: I define a &lt;code&gt;Geofence&lt;/code&gt; object with a &lt;code&gt;PendingIntent&lt;/code&gt;, and the system handles the hardware-level monitoring. When the boundary is crossed, the system sends an intent to my &lt;code&gt;BroadcastReceiver&lt;/code&gt;, which then triggers the sound profile change.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofence = Geofence.Builder()&lt;br&gt;
    .setRequestId("work_office")&lt;br&gt;
    .setCircularRegion(lat, lon, 100f)&lt;br&gt;
    .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER or Geofence.GEOFENCE_TRANSITION_EXIT)&lt;br&gt;
    .setExpirationDuration(Geofence.NEVER_EXPIRE)&lt;br&gt;
    .build()&lt;/p&gt;

&lt;p&gt;By using &lt;code&gt;PendingIntent&lt;/code&gt; with a &lt;code&gt;BroadcastReceiver&lt;/code&gt;, I avoid keeping a service alive in the background. The receiver wakes up, performs the light task—adjusting the &lt;code&gt;AudioManager&lt;/code&gt;—and then immediately terminates. The state management is handled by a local Room database, ensuring that even if the system kills the app process after the receiver finishes, the next trigger will simply pull the correct configuration from the database. It is efficient, robust, and respects the user's hardware constraints.&lt;/p&gt;

&lt;p&gt;What surprised me most during this development was the "flapping" issue. When you place a geofence around a small area, like a specific office building or a prayer hall, the GPS signal can drift. If you are standing near the boundary, your phone might report that you have exited and entered the zone several times in the span of a few minutes. If your logic isn't prepared for this, your phone will start toggling between silent and normal modes rapidly, causing the notification sound to chime or the screen to wake up repeatedly. This was a nightmare to debug because it only happened in specific locations with poor signal reception.&lt;/p&gt;

&lt;p&gt;I initially thought about adding a delay inside my &lt;code&gt;BroadcastReceiver&lt;/code&gt;, but that would keep the process alive longer than necessary, which I was trying to avoid. Instead, I had to implement a debounce logic inside the database update layer. Before the app executes a sound profile change, it checks the timestamp of the last transition. If the last change occurred within the last two minutes, the app ignores the new signal. It sounds simple, but it required me to build a proper state machine rather than just a linear trigger. If I were starting over, I would have integrated this debounce logic into the &lt;code&gt;Geofence&lt;/code&gt; setup itself using &lt;code&gt;setLoiteringDelay()&lt;/code&gt;. The documentation mentions this, but I ignored it because I thought it only applied to entering. It turns out that setting a loitering delay is the most effective way to prevent false positives when you are just lingering near a boundary. I had to learn the hard way that the system's built-in filtering is always more efficient than anything I can write in user-space code.&lt;/p&gt;

&lt;p&gt;Another lesson was the importance of the &lt;code&gt;Priority&lt;/code&gt; field in my database. In my early versions, if two geofences overlapped—for example, one for my office and one for a nearby building—the sound settings would fight each other. The logic would oscillate between silent and vibrate. I had to build a priority-based ranking system where only the highest-priority rule can dictate the sound state at any given time. This taught me that automation is not just about triggers; it is about state conflict resolution. When you have multiple rules running, you must treat the sound profile as a single shared resource that requires a clear locking or priority mechanism.&lt;/p&gt;

&lt;p&gt;As developers, we often obsess over the "happy path"—the code that works when the GPS is accurate and the user follows the expected routine. But true reliability in mobile development comes from handling the messy reality of hardware limitations. We need to respect the battery, anticipate the signal drift, and build systems that can fail gracefully. If your background task takes more than a few milliseconds, you are probably doing too much. Always look for ways to push the work back onto the system level, utilizing the APIs that allow the OS to manage resources on your behalf.&lt;/p&gt;

&lt;p&gt;Automation should be invisible. It should be a utility that you set once and forget completely, like the auto-brightness feature on your phone. If you are building tools for Android, focus on the user's intent rather than the implementation mechanics. By leveraging the &lt;code&gt;GeofencingClient&lt;/code&gt; and implementing a robust state machine to handle transitions, I was able to build Muffle, an app that manages sound profiles without the user ever needing to open it again. You can see how I approached these challenges at &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;, where I have applied these principles to ensure the app remains silent and efficient, just like the phone should be.&lt;/p&gt;

</description>
      <category>android</category>
      <category>androiddev</category>
      <category>kotlin</category>
      <category>mobiledev</category>
    </item>
    <item>
      <title>Architecting a 100% Offline Geofencing Engine for Android</title>
      <dc:creator>Haseeb</dc:creator>
      <pubDate>Sun, 05 Jul 2026 01:42:04 +0000</pubDate>
      <link>https://dev.to/haseebthedev0/architecting-a-100-offline-geofencing-engine-for-android-4mib</link>
      <guid>https://dev.to/haseebthedev0/architecting-a-100-offline-geofencing-engine-for-android-4mib</guid>
      <description>&lt;p&gt;It happened during a quiet afternoon at the local library. I was deep into a debugging session when my phone suddenly decided to blast a notification sound at full volume. The silence of the room shattered, and twenty people turned their heads in unison. I fumbled to silence it, but the delay was enough. That specific, sinking feeling of social friction—the kind that happens when your device refuses to respect the environment you are in—stayed with me long after I left that room.&lt;/p&gt;

&lt;p&gt;We all have these moments. You are in a meeting, a lecture, or a place of worship, and you simply forget to toggle that silent switch. Or worse, you silence it for the event and then completely forget to turn it back on, missing urgent calls for the rest of the day. The existing solutions on the Play Store were either bloated with telemetry trackers or relied entirely on cloud-based triggering, which felt like a massive privacy trade-off. I wanted a way to manage sound profiles based on location without my device constantly pinging a server to figure out where I was.&lt;/p&gt;

&lt;p&gt;The friction isn't just about the silence; it is about the mental load. If I have to manually check my phone to see if it is in the right mode, the automation has already failed its primary purpose. I needed a system that could handle geofencing, time-based triggers, and even calculated prayer times, all while remaining strictly offline. No analytics, no server-side lookups, and definitely no battery drain that would make the user regret installing the app in the first place.&lt;/p&gt;

&lt;p&gt;To build this, I leaned heavily on the &lt;code&gt;GeofencingClient&lt;/code&gt; provided by Google Play Services, but I had to wrap it in a custom logic layer to prevent it from eating the battery. The primary challenge with geofencing on Android is the balance between accuracy and wake-locks. If you set the responsiveness too tight, the GPS radio stays active for too long, draining the battery in a few hours. If you make it too loose, the user walks five minutes into their meeting before the silent mode triggers.&lt;/p&gt;

&lt;p&gt;I opted for a hybrid approach using &lt;code&gt;PendingIntent&lt;/code&gt; to wake the receiver only when the boundary transition occurred. The critical technical decision was to avoid keeping a foreground service running for the GPS itself. Instead, I register geofences as ephemeral triggers. When the system detects an entry or exit event, it broadcasts to my &lt;code&gt;BroadcastReceiver&lt;/code&gt;, which then checks the &lt;code&gt;AudioManager&lt;/code&gt; to set the &lt;code&gt;RINGER_MODE&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;kotlin&lt;br&gt;
val geofencingRequest = GeofencingRequest.Builder().apply {&lt;br&gt;
    addGeofences(geofenceList)&lt;br&gt;
    setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)&lt;br&gt;
}.build()&lt;/p&gt;

&lt;p&gt;val intent = PendingIntent.getBroadcast(context, 0, &lt;br&gt;
    Intent(context, GeofenceBroadcastReceiver::class.java), &lt;br&gt;
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)&lt;/p&gt;

&lt;p&gt;geofencingClient.addGeofences(geofencingRequest, intent)&lt;/p&gt;

&lt;p&gt;By keeping the state logic inside an offline database—using Room—the app doesn't have to query the network to see if a routine should be active. It just compares the current system time and location against the local SQLite store. This decoupling of the trigger (the geofence) from the action (the sound profile) allowed me to create a priority system. If a user is in a location that overlaps with a specific time-based routine, the app evaluates the priority integer assigned to each routine. The highest priority wins, ensuring that a 'Meeting' routine always overrides a general 'Work' routine, regardless of when they were triggered.&lt;/p&gt;

&lt;p&gt;What surprised me most during development was how much the &lt;code&gt;LocationManager&lt;/code&gt; behaves differently across manufacturers. I assumed that if I requested a geofence, the OS would handle it uniformly. I was wrong. On some devices, especially those with aggressive battery management like some older Huawei or Xiaomi models, the system would kill the &lt;code&gt;GeofencingClient&lt;/code&gt; background tasks if the user hadn't opened the app in a few days. My initial implementation relied on standard background execution, which failed miserably because the OS assumed the app was idle and zapped the registered geofences to save power.&lt;/p&gt;

&lt;p&gt;To fix this, I had to implement a 'Resurrection' logic. Every time the phone reboots, I use a &lt;code&gt;BOOT_COMPLETED&lt;/code&gt; receiver to re-register all geofences from the local database. I also added a check that runs on a &lt;code&gt;PeriodicWorkRequest&lt;/code&gt; via &lt;code&gt;WorkManager&lt;/code&gt; to ensure that if the geofences were dropped by the OS, they get re-added. This was a hard lesson in Android lifecycle management: never trust the OS to keep your background tasks alive indefinitely. You have to be proactive about re-establishing your state after any system event.&lt;/p&gt;

&lt;p&gt;Another assumption I got wrong was that GPS is always the most accurate trigger. In indoor environments, GPS signal degradation is significant. If a user is in a basement office, the geofence might 'flicker' as the GPS coordinates drift, causing the phone to repeatedly toggle between silent and normal modes. I solved this by implementing a 'hysteresis' buffer. The app now requires a transition to be sustained for at least 30 seconds before it fires the &lt;code&gt;AudioManager&lt;/code&gt; change. This prevents rapid toggling and saves the user from the annoyance of a phone that cannot decide what mode to stay in.&lt;/p&gt;

&lt;p&gt;If I were starting over, I would move away from the standard &lt;code&gt;GeofencingClient&lt;/code&gt; for everything. For highly localized triggers, like a specific desk in an office, I would experiment with Bluetooth LE beacon scanning. The GPS approach is excellent for general areas, but it is imprecise for small-scale indoor environments. However, for a general-purpose utility, the current implementation provides the best balance of battery efficiency and performance without requiring the user to carry extra hardware.&lt;/p&gt;

&lt;p&gt;For any Android developer working on background tasks, the biggest takeaway is this: minimize your wake-locks. Android is designed to punish apps that keep the CPU awake. Use the &lt;code&gt;WorkManager&lt;/code&gt; for persistent tasks and trust the system's scheduling where possible. If you need to perform an action based on location, do the heavy lifting in a background thread or a Worker, and only touch the UI or system settings when absolutely necessary. Most developers try to do too much inside the &lt;code&gt;BroadcastReceiver&lt;/code&gt;, which leads to ANRs and battery drain complaints.&lt;/p&gt;

&lt;p&gt;Building Muffle has taught me that users value reliability over a massive feature list. They want an app that 'just works' and stays out of their way. By focusing on local-first data and battery-conscious triggers, I managed to build something that feels like an extension of the Android OS rather than an intrusive background process. If you are struggling with similar issues or just want to see how I handled the prayer time calculations alongside the location logic, you can take a look at the implementation details here: &lt;a href="https://play.google.com/store/apps/details?id=com.muffle.app" rel="noopener noreferrer"&gt;https://play.google.com/store/apps/details?id=com.muffle.app&lt;/a&gt;. It is a work in progress, but it’s a solution that finally lets me sit in meetings without that lingering fear of a loud, misplaced ringtone.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>androiddev</category>
      <category>mobiledev</category>
    </item>
  </channel>
</rss>
