How to Use @HiltWorker with AssistedInject in Android WorkManager
The Problem
I was setting up background sync jobs in an Android app when I hit a wall. My Worker needed both runtime parameters (Context, WorkerParameters) and compile-time dependencies (Repository, AnalyticsService). Standard Hilt @Inject wouldn’t work.
error: [Dagger/MissingBinding] android.content.Context cannot be providedwithout an @Provides-annotated method.The issue is fundamental: Workers are created by WorkManager at runtime, not by Hilt at compile time. Hilt can’t inject into a class it doesn’t instantiate.
Why Standard @Inject Fails
When I tried this:
@HiltWorkerclass SyncWorker @Inject constructor( private val context: Context, // Runtime parameter private val workerParams: WorkerParameters, // Runtime parameter private val repository: DataRepository // Hilt dependency) : CoroutineWorker(context, workerParams) { // ...}The build failed because:
- Context and WorkerParameters are runtime values - WorkManager creates them when enqueueing work
- Hilt can’t provide runtime parameters - It only knows about compile-time dependencies
- @HiltWorker alone isn’t enough - It tells Hilt to generate a factory, but the factory still needs to receive runtime values
This is the core tension: dependency injection expects all dependencies at compile time, but Workers require runtime parameters.
The Solution: @AssistedInject
The correct pattern uses @AssistedInject for the constructor and @Assisted for runtime parameters:
@HiltWorkerclass SyncWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted private val workerParams: WorkerParameters, private val repository: DataRepository, // Hilt-injected private val analytics: AnalyticsService // Hilt-injected) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result { val data = repository.fetchData() analytics.trackSync() return Result.success() }}Here’s what each annotation does:
| Annotation | Purpose | Applied To |
|---|---|---|
@HiltWorker | Tells Hilt to generate a factory for this Worker | Class |
@AssistedInject | Marks constructor for assisted injection | Constructor |
@Assisted | Marks parameters provided at runtime | Context, WorkerParameters |
| No annotation | Hilt provides these dependencies | Repository, Analytics |
The distinction is crucial: @Assisted parameters come from WorkManager at runtime, while unannotated parameters come from Hilt at compile time.
Setting Up HiltWorkerFactory
The Worker class alone isn’t enough. I also needed to configure the Application to use Hilt’s WorkerFactory.
Step 1: Update Application Class
@HiltAndroidAppclass MyApplication : Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration get() = Configuration.Builder() .setWorkerFactory(workerFactory) .build()}Step 2: Disable Default WorkManager Initializer
In AndroidManifest.xml, remove the default initializer:
<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge">
<!-- Disable default WorkManager initializer --> <meta-data android:name="androidx.work.WorkManagerInitializer" tools:node="remove" /></provider>This step is critical. Without it, WorkManager uses its default factory instead of Hilt’s factory, and your @AssistedInject constructor won’t be invoked.
Enqueueing Work
Once configured, enqueuing work works the same as always:
val workRequest = OneTimeWorkRequestBuilder<SyncWorker>() .setConstraints( Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() ) .build()
WorkManager.getInstance(context).enqueue(workRequest)WorkManager creates the Worker using Hilt’s factory, which:
- Receives Context and WorkerParameters from WorkManager
- Gets Repository and AnalyticsService from Hilt’s dependency graph
- Calls your
@AssistedInjectconstructor with all parameters
Common Mistakes
I made these mistakes when first implementing this pattern.
Mistake 1: Using @Inject Instead of @AssistedInject
@HiltWorkerclass SyncWorker @Inject constructor( // Should be @AssistedInject @Assisted private val context: Context, // ...)Result: Build fails with “Dagger does not support injection into private constructors” or similar errors.
Mistake 2: Forgetting @Assisted on Runtime Parameters
@HiltWorkerclass SyncWorker @AssistedInject constructor( private val context: Context, // Missing @Assisted private val workerParams: WorkerParameters, // Missing @Assisted private val repository: DataRepository)Result: Hilt tries to provide Context and WorkerParameters from the dependency graph, which fails.
Mistake 3: Not Disabling Default Initializer
If you forget to remove the default WorkManagerInitializer, the app will crash or silently use the wrong factory.
Mistake 4: Not Implementing Configuration.Provider
@HiltAndroidAppclass MyApplication : Application() { // Should implement Configuration.Provider @Inject lateinit var workerFactory: HiltWorkerFactory // Missing workManagerConfiguration override}Result: WorkManager uses default factory, Hilt injection doesn’t happen.
@Inject vs @AssistedInject: When to Use Which
The pattern is clear once you understand the distinction:
flowchart TD A[Need to inject dependencies?] --> B{Who creates the instance?} B -->|Hilt creates it| C[Use @Inject] B -->|Framework creates it| D{Need runtime params?} D -->|No| E[Use @Inject in factory] D -->|Yes| F[Use @AssistedInject]
C --> G[Example: ViewModel, Repository] E --> H[Example: Fragment with FragmentFactory] F --> I[Example: Worker, Activity]Use @Inject when:
- Hilt controls the lifecycle (ViewModels, Repositories, Services)
- All dependencies are known at compile time
Use @AssistedInject when:
- A framework creates the instance (Workers, Activities)
- You need runtime parameters mixed with compile-time dependencies
Passing Additional Runtime Data
Sometimes you need to pass custom data beyond Context and WorkerParameters. Use inputData:
// Enqueueing with input dataval workRequest = OneTimeWorkRequestBuilder<SyncWorker>() .setInputData( workDataOf( "userId" to userId, "syncType" to "full" ) ) .build()
// Reading in Worker@HiltWorkerclass SyncWorker @AssistedInject constructor( @Assisted private val context: Context, @Assisted private val workerParams: WorkerParameters, private val repository: DataRepository) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result { val userId = inputData.getString("userId") val syncType = inputData.getString("syncType")
repository.syncUser(userId, syncType) return Result.success() }}This pattern keeps the constructor clean while still allowing runtime data to flow into the Worker.
The Architecture
Here’s how all the pieces connect:
sequenceDiagram participant App as Application participant WM as WorkManager participant HF as HiltWorkerFactory participant Hilt as Hilt Component participant W as SyncWorker
Note over App: 1. App implements Configuration.Provider App->>HF: Inject HiltWorkerFactory
Note over WM: 2. Enqueue work request WM->>HF: createWorker(context, params, workerClass) HF->>Hilt: Get compile-time dependencies Hilt-->>HF: Repository, AnalyticsService HF->>W: @AssistedInject(context, params, repo, analytics) W-->>WM: Worker instance readyThe HiltWorkerFactory acts as a bridge between WorkManager’s runtime requirements and Hilt’s compile-time dependency graph.
Why This Matters
This pattern came up in a senior-level Android quiz that many experienced developers struggled with. One commenter noted that WorkManager dependency injection is “something most devs only discover during a production audit.”
The reason it trips people up is that it combines several concepts:
- Dependency injection fundamentals
- Android lifecycle management
- WorkManager’s factory pattern
- Hilt’s code generation
Each concept is understandable alone, but combining them requires understanding how all the pieces interact.
Summary
The @HiltWorker + @AssistedInject pattern solves a specific problem: injecting compile-time dependencies into classes that require runtime parameters.
Key points:
- Workers are created by WorkManager, not Hilt, so standard
@Injectdoesn’t work @AssistedInjectlets you mix runtime parameters (@Assisted) with Hilt dependencies- You must configure
HiltWorkerFactoryin your Application class - Disable the default WorkManager initializer in the manifest
- Use
inputDatafor additional runtime values beyond Context and WorkerParameters
This pattern is essential for production-grade background work in Android. Without it, you’re stuck choosing between no dependency injection or manual dependency passing, both of which create maintenance problems as your app grows.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments