<?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: Hakim</title>
    <description>The latest articles on DEV Community by Hakim (@ahura_mazda).</description>
    <link>https://dev.to/ahura_mazda</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3972166%2F3d85c670-6cd0-4bf9-8093-107a42b5e1ec.jpeg</url>
      <title>DEV Community: Hakim</title>
      <link>https://dev.to/ahura_mazda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahura_mazda"/>
    <language>en</language>
    <item>
      <title>Injecting WorkManager into ViewModels with Dagger Hilt. No Context, No Boilerplate, Always a WorkQuery Back</title>
      <dc:creator>Hakim</dc:creator>
      <pubDate>Wed, 10 Jun 2026 09:13:00 +0000</pubDate>
      <link>https://dev.to/ahura_mazda/injecting-workmanager-into-viewmodels-with-dagger-hilt-no-context-no-boilerplate-always-a-chj</link>
      <guid>https://dev.to/ahura_mazda/injecting-workmanager-into-viewmodels-with-dagger-hilt-no-context-no-boilerplate-always-a-chj</guid>
      <description>&lt;p&gt;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 &lt;code&gt;WorkManager.getInstance(context)&lt;/code&gt; inside ViewModels, "passing &lt;code&gt;Context&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;This tutorial shows how to build a clean, injectable &lt;code&gt;WorkManagerHandler&lt;/code&gt; using Dagger Hilt, a single interface that any ViewModel can receive through constructor injection, with zero &lt;code&gt;Context&lt;/code&gt; and a guaranteed &lt;code&gt;WorkQuery&lt;/code&gt; callback on every call so you always know what to observe.&lt;/p&gt;

&lt;p&gt;By the end, you'll have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;code&gt;WorkManager&lt;/code&gt; singleton provided through Hilt&lt;/li&gt;
&lt;li&gt;A custom &lt;code&gt;Configuration.Provider&lt;/code&gt; that plugs Hilt's &lt;code&gt;HiltWorkerFactory&lt;/code&gt; into WorkManager at initialization&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;WorkManagerHandler&lt;/code&gt; interface with a &lt;code&gt;WorkManagerHandlerImpl&lt;/code&gt; that encapsulates enqueueing, chaining, and query registration&lt;/li&gt;
&lt;li&gt;ViewModels that declare &lt;code&gt;WorkManagerHandler&lt;/code&gt; as a plain constructor dependency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: familiarity with Dagger Hilt basics, Jetpack WorkManager fundamentals, and Kotlin coroutines. A working Android project with Hilt already configured is assumed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1: Application Setup and Custom Configuration.Provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, WorkManager initializes itself automatically using its own internal factory. The problem is that Hilt-injected workers need Hilt's factory &lt;code&gt;HiltWorkerFactory&lt;/code&gt; to resolve their &lt;code&gt;@Inject&lt;/code&gt; constructor dependencies. If you let WorkManager self-initialize, your workers won't have access to any of your Hilt bindings.&lt;/p&gt;

&lt;p&gt;The fix is to disable auto-initialization and take manual control via &lt;code&gt;Configuration.Provider&lt;/code&gt; and &lt;code&gt;AndroidManifest.xml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Disable auto-initialization&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;AndroidManifest.xml&lt;/code&gt;, remove WorkManager's default initializer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;application&lt;/span&gt; &lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;provider&lt;/span&gt;
        &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"androidx.startup.InitializationProvider"&lt;/span&gt;
        &lt;span class="na"&gt;android:authorities=&lt;/span&gt;&lt;span class="s"&gt;"${applicationId}.androidx-startup"&lt;/span&gt;
        &lt;span class="na"&gt;android:exported=&lt;/span&gt;&lt;span class="s"&gt;"false"&lt;/span&gt;
        &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"merge"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;meta-data&lt;/span&gt;
            &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"androidx.work.WorkManagerInitializer"&lt;/span&gt;
            &lt;span class="na"&gt;android:value=&lt;/span&gt;&lt;span class="s"&gt;"androidx.startup"&lt;/span&gt;
            &lt;span class="na"&gt;tools:node=&lt;/span&gt;&lt;span class="s"&gt;"remove"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/provider&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Jetpack Startup to skip the default WorkManager setup so you can wire it yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Implement Configuration.Provider in your Application class&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@HiltAndroidApp&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomApp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Inject&lt;/span&gt;
    &lt;span class="k"&gt;lateinit&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;workerFactory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HiltWorkerFactory&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workManagerConfiguration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workManagerConfiguration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setWorkerFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workerFactory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noting here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@HiltAndroidApp&lt;/code&gt;triggers Hilt's code generation and makes the  &lt;code&gt;Application&lt;/code&gt; class an injection entry point, this is what makes &lt;code&gt;@Inject lateinit var workerFactory&lt;/code&gt; work.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HiltWorkerFactory&lt;/code&gt; is Hilt's bridge into WorkManager. When WorkManager instantiates a worker, it will use this factory, which knows how to satisfy &lt;code&gt;@Inject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;We call &lt;code&gt;WorkManager.initialize()&lt;/code&gt; manually in &lt;code&gt;onCreate()&lt;/code&gt;, after Hilt has performed field injection, which means workerFactory is guaranteed to be non-null by the time it's used.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why this matters for the rest of the tutorial&lt;/strong&gt;&lt;br&gt;
Without this setup, any &lt;code&gt;@AssistedInject&lt;/code&gt; or &lt;code&gt;@Inject&lt;/code&gt; constructor in a worker class will silently fail at runtime, &lt;code&gt;WorkManager&lt;/code&gt; will either crash or fall back to a no-arg constructor it can't find. Getting this right first means everything built on top of it will actually work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2: Providing the WorkManager Singleton and Writing Your First CoroutineWorker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;Application&lt;/code&gt; class wired up, the next step is making the &lt;code&gt;WorkManager&lt;/code&gt; instance available across the app through Hilt, and writing a worker that can actually receive injected dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Provide the WorkManager singleton&lt;/strong&gt;&lt;br&gt;
In your Hilt module, add a &lt;code&gt;@Provides&lt;/code&gt; binding for &lt;code&gt;WorkManager&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="nd"&gt;@InstallIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SingletonComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Provides&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;provideWorkManagerInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nd"&gt;@ApplicationContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WorkManager&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WorkManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Singleton&lt;/code&gt; ensures the entire app shares one &lt;code&gt;WorkManager&lt;/code&gt; instance, this is important because WorkManager itself is a singleton under the hood, and wrapping it consistently avoids subtle state mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@ApplicationContext&lt;/code&gt; is a Hilt built-in qualifier. It gives you the application'&lt;code&gt;Context&lt;/code&gt; without you having to pass it manually anywhere. This is the last time &lt;code&gt;Context&lt;/code&gt; appears directly in this tutorial.&lt;/li&gt;
&lt;li&gt;This binding is what gets injected into &lt;code&gt;WorkManagerHandlerImpl&lt;/code&gt; later. The ViewModel never touches &lt;code&gt;WorkManager&lt;/code&gt; directly.
&lt;strong&gt;2. Write your first CoroutineWorker with Hilt injection&lt;/strong&gt;
A regular Worker subclass can't receive Hilt injections through a normal &lt;code&gt;@Inject&lt;/code&gt; constructor because &lt;code&gt;WorkManager&lt;/code&gt; controls instantiation. The solution is &lt;code&gt;@HiltWorker&lt;/code&gt; combined with @AssistedInject:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@HiltWorker&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YourWorker&lt;/span&gt; &lt;span class="nd"&gt;@AssistedInject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@Assisted&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Assisted&lt;/span&gt; &lt;span class="n"&gt;workerParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkerParameters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;someDependency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;SomeDependency&lt;/span&gt;  &lt;span class="c1"&gt;// injected by Hilt&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;CoroutineWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workerParams&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;doWork&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Result&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;coroutineScope&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;//Do some work&lt;/span&gt;
        &lt;span class="nc"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@HiltWorker&lt;/code&gt; marks the class so Hilt's code generator knows to produce a factory for it. Without this, &lt;code&gt;HiltWorkerFactory&lt;/code&gt; won't know the worker exists.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@AssistedInject&lt;/code&gt; is used because WorkManager must provide &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;workerParams&lt;/code&gt; at runtime;Hilt can't know those at compile time. &lt;code&gt;@Assisted&lt;/code&gt; marks those two parameters as runtime-provided; everything else (someDependency) is resolved from the Hilt graph normally.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The worker is ready. The singleton is in the graph. Next we build the interface that ViewModels will actually talk to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 3: The WorkManagerHandler Interface and Its Implementation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the core of the pattern. Instead of ViewModels calling &lt;code&gt;WorkManager&lt;/code&gt; directly, they talk to a &lt;code&gt;WorkManagerHandler&lt;/code&gt; interface. The implementation handles enqueueing, chaining, and always hands a WorkQuery back so the caller knows exactly what to observe.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Define the interface&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initializeYourWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeWorkInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design decisions worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every method takes a &lt;code&gt;workQuery: (WorkQuery) -&amp;gt;&lt;/code&gt; Unit callback. This is the contract: &lt;strong&gt;you will always get a&lt;/strong&gt; &lt;code&gt;WorkQuery&lt;/code&gt; &lt;strong&gt;back&lt;/strong&gt;, no matter which worker you trigger. The ViewModel uses it to observe work state without knowing anything about IDs or tags internally.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Data&lt;/code&gt; is WorkManager's key-value input container. Keeping it as the parameter type rather than raw primitives means the handler stays agnostic, it doesn't need to know what the data means, only that it gets passed into the worker.&lt;/li&gt;
&lt;li&gt;The interface has no &lt;code&gt;Context&lt;/code&gt;, no &lt;code&gt;WorkManager&lt;/code&gt;, no lifecycle references. It is a pure behavioral contract.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Implement the interface&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManager&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initializeFiltersWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneTimeWorkRequestBuilder&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;YourWorker&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setInputData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;workManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromIds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeWorkInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Flow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;workManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getWorkInfosFlow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breaking this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@Inject constructor&lt;/code&gt; with &lt;code&gt;WorkManager&lt;/code&gt; as the only parameter, Hilt resolves this from the singleton we provided in Part 2. No &lt;code&gt;Context&lt;/code&gt; needed anywhere here.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WorkQuery.fromIds()&lt;/code&gt; is built immediately after enqueueing, before the callback fires. This means the ViewModel receives the query in the same call stack as the enqueue, there is no race window where work is running but unobservable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Bind the implementation in your Hilt module&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The interface and implementation need to be connected. Add an abstract binding to your module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Module&lt;/span&gt;
&lt;span class="nd"&gt;@InstallIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SingletonComponent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerBindingsModule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Binds&lt;/span&gt;
    &lt;span class="nd"&gt;@Singleton&lt;/span&gt;
    &lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bindWorkManagerHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;@Binds&lt;/code&gt; requires an abstract function in an abstract module, it cannot live in the same object as &lt;code&gt;@Provides&lt;/code&gt; functions. If your existing module is an &lt;code&gt;object&lt;/code&gt;, either convert it to an &lt;code&gt;abstract class&lt;/code&gt; or create a separate module as shown above. Both approaches are valid.&lt;/p&gt;

&lt;p&gt;The handler is fully wired into the Hilt graph. Any class that declares &lt;code&gt;WorkManagerHandler&lt;/code&gt; in its constructor will get a fully functional implementation injected with no setup, no context, no boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 4: Injecting into a ViewModel and Observing Work State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where the pattern pays off. The ViewModel declares &lt;code&gt;WorkManagerHandler&lt;/code&gt; as a plain constructor dependency, triggers work through it, and observes the returned &lt;code&gt;WorkQuery&lt;/code&gt; all without touching &lt;code&gt;Context&lt;/code&gt;,&lt;code&gt;WorkManager&lt;/code&gt;, or any lifecycle plumbing directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Inject the handler into a ViewModel&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@HiltViewModel&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyViewModel&lt;/span&gt; &lt;span class="nd"&gt;@Inject&lt;/span&gt; &lt;span class="k"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workManagerHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_workState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_workState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;inputData&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;workDataOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;DATA_KEY&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;dataType&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;workManagerHandler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initializeYourWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;workQuery&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="nf"&gt;observeWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;observeWork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;workManagerHandler&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observeWorkInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workQuery&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;workInfoList&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                &lt;span class="n"&gt;_workState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;workInfoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstOrNull&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One note:&lt;br&gt;
The &lt;code&gt;WorkQuery&lt;/code&gt; that comes back from the handler callback is immediately handed to &lt;code&gt;observeWork()&lt;/code&gt;. Because the query is built right after enqueue inside the handler (as we saw in Part 3), by the time &lt;code&gt;observeWork()&lt;/code&gt; runs the work is already enqueued and observable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Observe work state in your Composable&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;MyScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MyViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;hiltViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;workState&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;workState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsStateWithLifecycle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nc"&gt;LaunchedEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"category_data"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workState&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ENQUEUED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RUNNING&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;CircularProgressIndicator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SUCCEEDED&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// render results&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nc"&gt;WorkInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CANCELLED&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// show error state&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common mistakes to avoid&lt;/strong&gt;&lt;br&gt;
Calling &lt;code&gt;WorkManager.getInstance()&lt;/code&gt; directly in a ViewModel. This bypasses the injected singleton and can produce a second instance if auto-initialization wasn't properly disabled. Always inject &lt;code&gt;WorkManagerHandler&lt;/code&gt; and let the handler own the WorkManager reference.&lt;/p&gt;

&lt;p&gt;Forgetting &lt;code&gt;tools:node="remove"&lt;/code&gt; in the manifest. If the default initializer is still active, WorkManager self-initializes before HiltWorkerFactory is ready. Workers will fail to instantiate any @Inject dependencies silently.&lt;/p&gt;

&lt;p&gt;Using &lt;code&gt;@Inject&lt;/code&gt; instead of &lt;code&gt;@AssistedInject&lt;/code&gt; in a worker. WorkManager provides &lt;code&gt;context&lt;/code&gt; and &lt;code&gt;workerParams&lt;/code&gt; at runtime, Hilt cannot know them at compile time. Without &lt;code&gt;@AssistedInject&lt;/code&gt; and &lt;code&gt;@Assisted&lt;/code&gt;, the worker constructor will either fail to compile or crash at runtime.&lt;/p&gt;

&lt;p&gt;Building &lt;code&gt;WorkQuery&lt;/code&gt; before calling &lt;code&gt;enqueue()&lt;/code&gt;. The request ID exists as soon as the request object is built, but the work isn't registered with WorkManager until after &lt;code&gt;enqueue()&lt;/code&gt;. Always build the query after the enqueue call to guarantee the work is observable immediately.&lt;/p&gt;

&lt;p&gt;Swapping &lt;code&gt;WorkManagerHandler&lt;/code&gt; and &lt;code&gt;WorkManagerHandlerImpl&lt;/code&gt; or assuming it's the same in the @Binds declaration. This is one of the most common Hilt mistakes and the error message does not always make it obvious. The rule is: the return type is the interface, the parameter is the implementation — not the other way around.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WRONG — will fail to compile or inject incorrectly&lt;/span&gt;
&lt;span class="nd"&gt;@Binds&lt;/span&gt;
&lt;span class="nd"&gt;@Singleton&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bindWorkManagerHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt;         &lt;span class="c1"&gt;// ❌ interface as parameter&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt;            &lt;span class="c1"&gt;// ❌ implementation as return type&lt;/span&gt;

&lt;span class="nc"&gt;Or&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="nd"&gt;@Binds&lt;/span&gt;
&lt;span class="nd"&gt;@Singleton&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bindWorkManagerHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt;         &lt;span class="c1"&gt;// ❌ implementation as return type&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt;            &lt;span class="c1"&gt;// ❌ implementation as return type&lt;/span&gt;


&lt;span class="c1"&gt;// CORRECT&lt;/span&gt;
&lt;span class="nd"&gt;@Binds&lt;/span&gt;
&lt;span class="nd"&gt;@Singleton&lt;/span&gt;
&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;bindWorkManagerHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;impl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandlerImpl&lt;/span&gt;     &lt;span class="c1"&gt;// ✅ concrete class Hilt knows how to build&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;WorkManagerHandler&lt;/span&gt;                &lt;span class="c1"&gt;// ✅ the type the rest of the graph asks for&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Wrapping up&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pattern covered in this tutorial gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single &lt;code&gt;WorkManagerHandler&lt;/code&gt; interface that any ViewModel can receive with no setup cost&lt;/li&gt;
&lt;li&gt;Workers that participate fully in the Hilt dependency graph via &lt;code&gt;@HiltWorker&lt;/code&gt; and &lt;code&gt;@AssistedInject&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A guaranteed &lt;code&gt;WorkQuery&lt;/code&gt; on every enqueue so the UI always has something to observe&lt;/li&gt;
&lt;li&gt;The ability to add as many workers as you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The boundary between WorkManager and the rest of your app is now the &lt;code&gt;WorkManagerHandler&lt;/code&gt; interface, which means it is also the boundary you can mock in tests.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>workmanager</category>
      <category>hilt</category>
      <category>android</category>
    </item>
  </channel>
</rss>
