AI 资讯
Architecting Location-Based Automation Without Killing the Battery
Opening hook 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. The problem 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. 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. The technical decision / implementation When I started building Muffle, the biggest challenge was the Geofencing API. The temptation is to use LocationManager and track the device's coordinates in real-time, but that’s an immediate death sentence for battery life. Instead, I opted for the GeofencingClient within the Google Play Services library. This is a crucial distinction: LocationManager gives you raw data that you have to pro
AI 资讯
Gson silent bug that Never Said a Word(Interview Prep)
Here's a bug that looks impossible until you understand Gson. You have this data class in your Pokedex app: data class PokemonStat ( @SerializedName ( "base_stat" ) val baseStat : Int , val stat : StatInfo ) A teammate cleans up the code and deletes what looks like a redundant line: data class PokemonStat ( val baseStat : Int , // @SerializedName removed val stat : StatInfo ) It compiles . No red errors. He runs the app — and every Pokemon's baseStat is 0 . Not the real 45 or 49. Just 0 . Everywhere. And Gson never threw an error, never logged a warning, never said a single word. If you can explain why this happens, you understand Gson better than most juniors. This is also a favorite interview question. Let's break it fully. What Gson actually does When the JSON comes back from the server, Gson goes key by key: Read a key from the JSON — say base_stat . Look for a Kotlin property with the exact same name . Found it? Pour the value in. Not found? Leave that property at its default value and move on. That's the entire matching game — exact name match, or nothing. Now look at the "cleaned up" class. The JSON key is base_stat . The Kotlin property is baseStat . Those are not the same string . Gson looks for a property called base_stat , doesn't find one, shrugs, and leaves baseStat at its default. The default for an Int is 0 . That's your bug. @SerializedName("base_stat") was never redundant. It was the sticky note telling Gson: "this property is called baseStat in Kotlin, but look for base_stat in the JSON." Delete the note, and Gson stops matching. Two ways to fix it: Rename the property to base_stat — works, but breaks Kotlin's camelCase convention. Put @SerializedName("base_stat") back — keeps the clean name and matches. This is the right one. But why 0 ? Why not a crash? This is the part that surprised me. A missing field feels like it should be an error. It isn't. Gson treats a missing key as allowed . It builds your object, fills the fields it found, and leaves
AI 资讯
Apple locked hearing assistance inside AirPods. So I built an open-source version for any earbuds.
In 2024, Apple shipped something genuinely great: AirPods Pro can run a clinical-style hearing test and then act as hearing assistance, tuned to your ears. People love it. There's just one catch — it needs an iPhone to set up, recent AirPods to run, and if you're on Android you get nothing. Meanwhile, the average pair of prescription hearing aids costs about $4,700 , and surveys show a $1,500 device is simply out of reach for more than half the people who need one. There are a billion-plus Android phones out there, most of them sitting next to a pair of ordinary earbuds that already contain everything you physically need: a microphone, a DAC, and speakers. The gap seemed absurd. So I've spent the past weeks building OpenHearing — a free, GPLv3 Android app that does the whole pipeline: Hearing check — a pure-tone test using the modified Hughson–Westlake staircase (the same adaptive up-down procedure audiologists use), per ear, per frequency. Or skip it and type in the numbers from a real audiogram. Sound profile — the results are fitted into a per-ear gain curve (half-gain rule for v1; NAL-NL2 is a pluggable strategy for later). Real-time assist — mic in, per-ear DSP, earbuds out. Quiet speech gets louder. Works with whatever earbuds you already own. No root, no special hardware. It is very deliberately not a medical device — no diagnosis, no treatment claims, big disclaimer before anything plays a tone. Think of it as the open, inspectable "gateway" tier below real hearing care. This post is about the three engineering decisions that turned out to matter most. 1. The safety-critical DSP is pure Kotlin — and that's the whole point An app that amplifies sound directly into human ears has exactly one unforgivable failure mode: being loud when it shouldn't be. So the entire signal chain is plain Kotlin with zero Android dependencies, hidden behind a tiny I/O interface. AudioRecord / AudioTrack is a dumb shell; everything that can hurt someone is JVM-testable: input → EQ
AI 资讯
Engineering Geofencing: Lessons in Android Battery and Location Accuracy
It happened during a quiet, solemn moment in a community prayer hall. I was sitting in the third row, reflecting, when suddenly, a high-pitched ringtone shattered the silence. It wasn't my phone, but the ripple effect of embarrassment was immediate. Everyone looked around, shifting uncomfortably. That collective tension is something we have all felt—a moment of human error that technology should have intercepted. I looked at my own device, feeling the familiar anxiety of whether I had remembered to flip the physical silent switch. It was then that I decided to stop relying on my own memory. We live in an era of hyper-connected devices, yet the most basic context-awareness—knowing where we are and how our phone should behave—remains manual. I found myself constantly toggling between 'Normal' and 'Silent' modes at the library, the office, and the gym. If I forgot, I was the person disrupting a meeting. If I remembered to mute it, I inevitably forgot to unmute it, missing urgent calls from family for hours. The existing solutions were either too bloated, requiring invasive cloud permissions, or they simply failed to trigger reliably when the screen was off. I needed a solution that was local, predictable, and battery-conscious. Building Muffle started with the realization that I had to master the GeofencingClient API without draining the user's battery. The primary challenge wasn't just triggering an event; it was doing so while the device was in a deep sleep state. I initially experimented with a standard LocationManager approach, polling GPS coordinates at set intervals. That was a disaster. It kept the radio active, pinged the GPS satellites constantly, and decimated the battery life in under four hours. It was an immediate non-starter for a production-ready application. I pivoted to the Geofencing API provided by Google Play Services, which leverages the fused location provider. This is significantly more efficient because the system handles the batching and hardwa
AI 资讯
Keeping background services alive: Lessons from building Muffle
Opening hook It happened during a quiet afternoon at the mosque. The imam was mid-sentence when a rhythmic, high-pitched ringtone cut through the silence like a knife. Every head turned. It was my phone. My heart sank as I scrambled to silence it, only to realize I had forgotten to flip the physical toggle before walking in. That moment of collective, disappointed glares burned. It wasn't just an annoyance; it was a total breakdown of my focus and a social failure I had accidentally caused because my phone couldn't manage itself. The problem We live in an era where our devices are supposedly 'smart,' yet they are remarkably bad at knowing when to keep quiet. We carry computers in our pockets that can calculate the exact position of the moon or stream 4K video, but they cannot inherently tell that we are in a meeting, a lecture, or a place of worship. You could argue that setting a manual schedule works, but life isn't static. Meetings run over, prayer times shift by a minute each day based on astronomical calculations, and spontaneous plans happen. I found myself constantly juggling the physical volume buttons. If I remembered to mute it, I inevitably forgot to unmute it afterward, missing urgent calls from family. If I didn't mute it, I was the person disrupting the room. I wanted a solution that respected the context of my location and the specific time of day without requiring me to touch my screen. The core friction is that Android is designed to restrict background processes to save battery, which is exactly what a silent-automation app needs to thrive. Getting the app to reliably trigger a volume change while the phone is sitting in a pocket, deep in Doze mode, became my primary development hurdle. The technical decision / implementation When I started building Muffle, I initially tried a standard Service with a Handler loop to check conditions. It worked fine while the screen was on, but as soon as the phone entered Doze mode, the OS aggressively throttled my
AI 资讯
The Future of KMP: Upgrading to Kotlin 2.3.20 and Compose 1.10.3
The Kotlin Multiplatform (KMP) ecosystem moves fast. To stay at the cutting edge, ImagePickerKMP has recently undergone a major architectural upgrade in version 1.0.42 , adopting the latest stable versions of Kotlin and Compose Multiplatform. For the latest requirements and installation guides, always refer to https://imagepickerkmp.dev/ . Major Version Upgrades The v1.0.42 release brings significant updates to the core dependencies of the library: Dependency New Version Previous Version Kotlin 2.3.20 2.1.x Compose Multiplatform 1.10.3 1.9.x Ktor 3.4.1 3.0.x Android Gradle Plugin 8.13.2 8.x Warning: Kotlin 2.3.x brings breaking ABI changes. Projects using Kotlin < 2.3.x will fail to compile with an "ABI version incompatible" error when using ImagePickerKMP 1.0.42. Why the Upgrade Matters Performance: Kotlin 2.3.20 includes numerous compiler optimizations that result in smaller and faster binaries for both Android and iOS. Stability: Compose Multiplatform 1.10.3 resolves several rendering issues on iOS and Desktop, providing a smoother user experience. Future-Proofing: By moving to these versions, ImagePickerKMP is ready for the upcoming features in the Kotlin roadmap. How to Upgrade Your Project To use the latest version of ImagePickerKMP, you must update your build.gradle.kts file: plugins { kotlin ( "multiplatform" ) version "2.3.20" id ( "org.jetbrains.compose" ) version "1.10.3" } dependencies { implementation ( "io.github.ismoy:imagepickerkmp:1.0.42" ) } If your project is not yet ready for Kotlin 2.3.x, you can continue using version 1.0.41 of the library, which maintains compatibility with older Kotlin versions. Conclusion Staying updated is crucial for security, performance, and developer happiness. ImagePickerKMP makes it easy to leverage the power of the latest Kotlin features while maintaining a simple, unified API for media picking. Explore the full API reference and new features at https://imagepickerkmp.dev/ . References [1]: ImagePickerKMP Documentati
AI 资讯
Keeping Android Services Alive Against OEM Battery Aggression
It was the middle of a Friday afternoon, and I was sitting in the front row of a local mosque. The room was deathly quiet, the kind of silence that amplifies every heartbeat. Suddenly, three rows behind me, a phone erupted with a loud, brassy ringtone that seemed to go on for an eternity. The man scrambled to silence it, his face turning bright red as he fumbled with his screen. I felt his humiliation deeply. In that moment, I realized that modern smartphones—despite their intelligence—are remarkably stupid when it comes to context-aware social etiquette. We live in a world of smart devices, yet we are still manually toggling our volume settings like it is 2005. I have spent years forgetting to silence my phone before a meeting, a lecture, or a quiet space, only to have it buzz loudly at the worst possible time. It is a friction point that feels trivial until it happens to you, at which point it becomes incredibly disruptive. Existing solutions often fall into two camps: over-engineered automation tools that require a computer science degree to configure, or basic calendar-sync apps that lack the nuance needed for things like location-based triggers or recurring religious observances. I wanted something that just worked, quietly, in the background, without requiring me to constantly open an app to double-check if my rules were still active. When I started building Muffle, I quickly realized that the greatest obstacle wasn't the logic of detecting a location or a prayer time—it was the operating system itself. Android, in its quest to squeeze every millisecond of battery life out of a device, has turned into a minefield for developers trying to keep background tasks alive. If you rely on a standard Service , the system will kill it within minutes as soon as the user turns the screen off. I needed a way to ensure that my background monitoring, especially for geofencing and prayer time calculations, stayed alive even when the phone was sitting in a pocket for hours. I
AI 资讯
Keeping Android Background Services Alive Against OEM Aggression
We have all been there: you build a utility app that relies on precise location or time-based triggers, only to find that it works perfectly on your Pixel but dies silently on a Samsung or Xiaomi device. When I started building Muffle, an app designed to automate sound profiles based on prayer times and GPS, I realized that standard AlarmManager usage wasn't enough to survive aggressive battery optimizations. The Problem with OEM Kill-Switches Modern Android versions enforce strict background execution limits. If your app isn't a high-priority foreground service, OEMs will frequently kill your process to save a few milliwatts of battery. For Muffle, if the process dies, the user misses their silent profile trigger, which defeats the entire purpose of the app. I had to move away from relying on a long-running background service and rethink my architecture entirely. Moving to WorkManager with Expedited Jobs Instead of a persistent service, I transitioned the core logic to WorkManager . By utilizing ExistingPeriodicWorkPolicy.UPDATE , I ensure that the scheduling remains consistent even across reboots. However, WorkManager alone can be delayed by Doze mode. To combat this, I implemented setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) for critical profile switches. This tells the system that the work is time-sensitive. kotlin val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES) .setConstraints(Constraints.Builder().build()) .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST) .build() Leveraging Foreground Services with Notifications For features requiring immediate precision—like geofencing—I had to accept that a persistent notification is non-negotiable. To keep the app from being perceived as 'spammy,' I designed the notification to be low-priority, showing only when a profile is actively being managed. I also had to handle the onTaskRemoved callback in my Service implementation. By calling startService again with a sticky
AI 资讯
Optimizing Geofence Transitions: Battery Efficient Background Logic in Android
We have all been there: a meeting starts, and suddenly your phone rings. I built Muffle to automate silent profiles, but the biggest hurdle wasn't the UI—it was making sure the app didn't destroy the user's battery while monitoring GPS coordinates. The Trap of Continuous Location Updates Early prototypes used LocationManager with frequent updates. This is the fastest way to get your app uninstalled. Keeping the GPS radio active in the background forces the device to wake the CPU constantly, leading to significant battery drain. To solve this, I moved away from active polling and shifted to the GeofencingClient API. Leveraging GeofencingClient for Passive Monitoring Instead of calculating distance from a point every few seconds, I transitioned to system-level geofencing. By defining circular regions around locations like the office or a mosque, the OS handles the monitoring at the hardware abstraction layer. kotlin val geofencingRequest = GeofencingRequest.Builder() .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER) .addGeofences(geofenceList) .build() This approach allows the OS to do the heavy lifting. The app stays in a dormant state until the location provider signals a transition. The kernel only wakes the app when the device enters or exits the radius. The Trade-off: Precision vs. Power Using GeofencingClient means accepting a slightly slower trigger time compared to raw GPS polling. Sometimes, there is a delay of a few seconds as the device wakes from a deep sleep state. For a utility like Muffle, this is a fair trade-off. Users prefer their phone to silence five seconds after entering a building rather than finding their battery dead by noon. To mitigate the delay, I combined geofencing with a secondary intent service that performs a final check once the geofence trigger hits, ensuring that we aren't just reacting to a momentary GPS jitter. Final Thoughts By offloading the monitoring to the platform's native geofencing API, I was able to keep Muffle
开源项目
🔥 andreknieriem / headunit-revived - Headunit App for displaying Android Auto
GitHub热门项目 | Headunit App for displaying Android Auto | Stars: 1,359 | 79 stars today | 语言: Kotlin
AI 资讯
The Big Lie in Mobile Privacy (And How We Fixed It)
The Big Lie in Mobile Privacy (And How We Fixed It) If you have an Android phone, you've seen the pop-up banners asking for your permission to track your data. You click "Reject All," assuming the app stops tracking you. Here is the dirty secret of the mobile app industry: It usually doesn't stop them. 🔍 The Pipeline Problem Traditional privacy tools are built like internet filters. When an app tries to send your data across the web to a data company, the privacy tool tries to block that specific web traffic. There is a massive flaw in this approach: The moment you open an app, hidden tracking packages (called SDKs) wake up instantly. They immediately copy your phone's ID, your location, and your usage habits into their internal memory. Even if a network filter blocks them from sending it right now, your data is already collected. The trackers just wait until the filter drops, or they find a workaround to leak it out later. You are forced to blindly trust that these third-party trackers will behave themselves. Spoiler alert: they don't. 🔒 CookiePrime: Locking the Front Door At CookiePrime, we got tired of the "illusion" of privacy. Founded by privacy industry veterans who witnessed how easily corporate trackers bypass traditional regulations, we decided to build a true privacy enforcement ecosystem . Instead of trying to catch your data as it flies out over the internet, our Android software stops trackers from waking up in the first place. Think of trackers like uninvited snoops at a party: Traditional tools try to grab the snoop's notebook after they've walked around your house and written down your secrets. CookiePrime locks the front door so the snoop never steps inside. The moment a CookiePrime-protected app starts, our engine runs a lightning-fast sweep — taking just 93 milliseconds — to identify every tracking script hidden inside the app. If a user says "No Tracking," CookiePrime instantly freezes those specific trackers on the spot. They can't collect data,
AI 资讯
97% of My App's Code Is in commonMain — A Field Report on Shipping 100% Compose Multiplatform
I shipped a small dev-news reader to Google Play with the entire client written in one Compose Multiplatform codebase — every screen in commonMain , no per-platform UI. This is an honest field report on what that actually costs in production: the numbers, the native seams, and the parts that still hurt (hi, iOS). The repo is open source (MIT), so everything here is checkable. TL;DR — For a content/list/detail app, CMP is comfortably production-ready on Android. 96.9% of the shared module is commonMain ; the native cost is concentrated in ~10 expect/actual seams. iOS compiles and renders, but isn't polished yet. The numbers Source set Files Lines Share commonMain 59 ~7,700 96.9% androidMain 2 123 1.6% iosMain 3 127 1.6% All 17 screens live in commonMain — trending list, aggregated feed, README detail, profile with paging, settings, favorites — and there isn't a single if (isAndroid) branch in the UI. The 3% that isn't shared The native cost isn't spread thinly across the codebase. It's concentrated in ~10 expect/actual seams, and this is the entire list: Platform info — app version, system language, User-Agent string System interaction — open URL, open app settings, share sheet Analytics — a trackEvent hook (Android → Aptabase; iOS is deliberately a no-op for now) WebView — the messy one (below) Everything that touches a platform API is small and enumerable. Everything else came for free. Why expect/actual and not an interface + DI? For these ~10 seams, expect/actual was the least ceremony: no DI wiring, and the compiler refuses to build until every target implements the declaration. The moment a seam has more than one implementation, or I'd want to fake it in tests, an interface in commonMain with injected impls is the better tool. For a fixed set of platform primitives, expect/actual wins on friction. The ugliest boundary: WebView I have two WebView paths, and I'll be precise because the repo is open: Rendering GitHub READMEs from an HTML string — inline expect/act
AI 资讯
I reverse-engineered my motorcycle's Bluetooth protocol to put Google Maps on the dashboard
My motorcycle has a Bluetooth instrument cluster. It pairs with the manufacturer's phone app and shows turn-by-turn navigation right on the dash, which sounds great until you actually use it. The nav is routed through a maps provider I don't love, the app is clunky, and there's no way to extend any of it. I kept thinking: it's just my bike talking to my phone over Bluetooth. How locked down can it really be? So one weekend I decided to find out, and a few weeks later I had Google Maps navigation running on the cluster through an app I wrote myself. Here's how that went. There are no docs Of course there aren't. It's a proprietary protocol, and the only reference that exists is the manufacturer's own app, in compiled form. So step one was just watching. I started with a GATT walk on the live bike, which is the Bluetooth equivalent of knocking on every door to see what's there. The cluster exposes one vendor service with two characteristics: one the phone writes to, one the bike sends notifications back on. That's the entire conversation surface. Then I captured the actual bytes going across. Android can log every Bluetooth packet through its HCI snoop log, so I paired the phone with the bike, rode around, and pulled the capture. Now I had real traffic, and absolutely no idea what any of it meant. Reading the app to read the protocol You can stare at hex forever and still guess wrong. The faster path was the app itself. I pulled the APK, ran it through JADX to decompile it, and got something close to readable source. Most of the class names weren't even obfuscated, which was a gift. From there it was cross-referencing: take a message I saw on the wire, find the code that builds it, and work out what each byte is. Frida helped a lot here. It lets you hook a running app and watch functions get called with their real arguments, so I could catch the exact moment the app turned "next turn is a left in 200m" into bytes and shipped them to the bike. Slowly the shape came out
AI 资讯
Mastering Design Principles: Dependency Inversion in Kotlin
Abstract In modern software engineering, writing code that simply "works" is only the first step. The real challenge lies in designing systems that are maintainable, scalable, and easy to test. This article explores the Dependency Inversion Principle (DIP), the final pillar of the SOLID design principles. Through a practical, real-world example in Kotlin, we will demonstrate how to transition from a tightly coupled architecture to an abstraction-based design. This shift dramatically improves our codebase, facilitates unit testing, and prepares our applications for future growth. Introduction: The Chaos of Coupling As applications grow, it is common to see how a minor change in a database schema or a third-party API triggers a domino effect, breaking unrelated parts of the system. This fragility is a direct consequence of tight coupling. Software design principles, particularly SOLID, were established to prevent this architectural decay. Today, we focus on the "D" in SOLID: the Dependency Inversion Principle (DIP). This principle establishes two core rules: High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces). Abstractions should not depend on details. Details (concrete implementations) should depend on abstractions. The Scenario: An E-commerce Payment Processor Imagine you are building the billing system for an online store. To process purchases, the system needs to connect to a payment gateway, such as PayPal. The Bad Way: Tight Coupling (Violating DIP) In this initial design, our high-level business logic (OrderProcessor) directly instantiates and depends on the concrete low-level class (PayPalService). // Low-level component (Concrete detail) class PayPalService { fun executePayment(amount: Double) { println("Processing payment of $$amount via PayPal API.") } } // High-level component (Business logic) class OrderProcessor { // Tight coupling: this class depends directly on a concrete implementation private val
开发者
Spring Boot 4.1 Adds gRPC Auto-Configuration, SSRF Mitigation, and Kotlin 2.3 Support
Broadcom released Spring Boot 4.1 on June 10, 2026, to deliver gRPC auto-configuration, HTTP-client SSRF mitigation, and upgrades to Kotlin 2.3. It also brings lazy datasource connections, async context propagation for @Async methods, and improved OpenTelemetry support. Uncharacteristically, Broadcom moved the releases twice, first from May 11-22 to June 1-5, then to June 8-12. By Karsten Silz
AI 资讯
⚠️ The Kotlin Multiplatform division-by-zero trap
If you write Kotlin Multiplatform code that involves integer division, you may have already hit this: the exact same expression behaves completely differently depending on which platform compiles it. 🐛 The problem Take this innocuous expression: val quotient = 12 / 0 val remainder = 12 % 0 On JVM and Native , both lines throw an ArithmeticException . That is the behavior most Kotlin developers expect and design around. On JavaScript , both lines execute without any exception and silently return 0 . Here is a concrete illustration drawn directly from the Kotlin test suites for each platform: // Kotlin/JS check ( 12 / 0 == 0 ) // passes — no exception check ( 12 % 0 == 0 ) // passes — no exception // Kotlin/JVM and Kotlin/Native val quotient : Result < Int > = runCatching { 12 / 0 } val remainder : Result < Int > = runCatching { 12 % 0 } check ( quotient . exceptionOrNull () is ArithmeticException ) // passes check ( remainder . exceptionOrNull () is ArithmeticException ) // passes Summary table: Expression JVM / Native JavaScript 12 / 0 ArithmeticException 0 12 % 0 ArithmeticException 0 🤔 Why it happens On Kotlin/JS, Int values are represented as JavaScript numbers, and 12 / 0 evaluates to Infinity while 12 % 0 evaluates to NaN . Kotlin/JS truncates Int arithmetic to 32 bits using JavaScript's | 0 operator, and per the ECMAScript ToInt32 conversion, both Infinity | 0 and NaN | 0 evaluate to 0 — so the division-by-zero result silently becomes 0 , with no exception thrown. JVM and Native follow Java's long-standing contract: integer division by zero is always an ArithmeticException . The practical consequence is that any guard you write and test on JVM — a try/catch(ArithmeticException) or a pre-condition check that relies on an exception — is silently bypassed when the same code runs on JS. No compile error, no warning, just a wrong result. ✅ The fix: Integer from Kotools Types 5.1.1 The Integer type in Kotools Types explicitly checks for a zero divisor before delegat
AI 资讯
Injecting WorkManager into ViewModels with Dagger Hilt. No Context, No Boilerplate, Always a WorkQuery Back
WorkManager is the right tool for deferrable, guaranteed background work in Android. But the default setup pushes you toward boilerplate fast: you end up calling WorkManager.getInstance(context) inside ViewModels, "passing Context where it doesn't belong", re-registering observers scattered across the codebase, and getting no consistent way to query the state of your enqueued work. This tutorial shows how to build a clean, injectable WorkManagerHandler using Dagger Hilt, a single interface that any ViewModel can receive through constructor injection, with zero Context and a guaranteed WorkQuery callback on every call so you always know what to observe. By the end, you'll have: A WorkManager singleton provided through Hilt A custom Configuration.Provider that plugs Hilt's HiltWorkerFactory into WorkManager at initialization A WorkManagerHandler interface with a WorkManagerHandlerImpl that encapsulates enqueueing, chaining, and query registration ViewModels that declare WorkManagerHandler as a plain constructor dependency The pattern scales cleanly as you add workers: each new worker is one method on the handler, and the ViewModel never knows or cares how work is scheduled underneath. Prerequisites : familiarity with Dagger Hilt basics, Jetpack WorkManager fundamentals, and Kotlin coroutines. A working Android project with Hilt already configured is assumed. Part 1: Application Setup and Custom Configuration.Provider By default, WorkManager initializes itself automatically using its own internal factory. The problem is that Hilt-injected workers need Hilt's factory HiltWorkerFactory to resolve their @Inject constructor dependencies. If you let WorkManager self-initialize, your workers won't have access to any of your Hilt bindings. The fix is to disable auto-initialization and take manual control via Configuration.Provider and AndroidManifest.xml 1. Disable auto-initialization In your AndroidManifest.xml , remove WorkManager's default initializer: <application ... > <