Introduction
When using a Worker (from the WorkManager library), you might have wondered how to inject dependencies with Hilt. In this tutorial, we will learn how to inject dependencies into our Worker.
Goals
At the end of the tutorial, you would have learned:
- How to inject dependencies into a Worker.
Tools Required
- Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 2.
Prerequisite Knowledge
- Intermediate Android.
- WorkManager library.
- Hilt.
Project Setup
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
Add the plugins below into the
plugins{}
block of the modulebuild.gradle
file.id 'kotlin-kapt' id 'dagger.hilt.android.plugin'
-
Add the dependencies below into the module
build.gradle
file. These are Hilt and WorkManager dependencies.//Work Manager implementation 'androidx.work:work-runtime-ktx:2.7.1' //Hilt implementation 'com.google.dagger:hilt-android:2.41' implementation 'androidx.hilt:hilt-work:1.0.0' kapt 'com.google.dagger:hilt-compiler:2.41' kapt 'androidx.hilt:hilt-compiler:1.0.0'
-
Add the Hilt Gradle plugin dependency to the Project build.gradle file.
buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.google.dagger:hilt-android-gradle-plugin:2.41' } }
-
Create an empty class called
ExampleDependency
in a file called ExampleDependency.kt. This will act as the dependency that we would later inject into our Worker.class ExampleDependency @Inject constructor()
-
Create a class called MyApplication that extends
Application
, and then annotate it with@HiltAndroidApp
.@HiltAndroidApp class MyApplication: Application() { }
-
Add the Application class name into your manifest’s
<application>
.android:name=".MyApplication"
-
Create a class called ExampleWorker using the code below. This is the Worker that we will inject ExampleDependency into later.
class ExampleWorker constructor( context: Context, workerParams: WorkerParameters ) : Worker(context, workerParams) { override fun doWork(): Result { return Result.success() } }
Project Overview
Our project is quite simple. We only have the bare minimum needed for Hilt to work with a Worker. The ExampleWorker class has two dependencies declared in its constructor, but they are only dependencies required by the super class Worker and not the ExampleDependency that we are trying to inject.
class ExampleWorker constructor(
context: Context,
workerParams: WorkerParameters
)
There is a little bit of boilerplate code that we must write in the next few steps to be able to inject ExampleDependency into our ExampleWorker.
Apply the @HiltWorker
Annotation
The first thing that we need to do is to apply @HiltWorker
to ExampleWorker.
@HiltWorker
class ExampleWorker constructor(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
Here are a few important things to note about this annotation:
- Your Worker is now available to be created by HiltWorkerFactory.
- Only dependencies scoped to
SingletonComponent
can be injected into your Worker.
Assisted Injection
Next, we need to apply the annotation @AssistedInject
to the constructor of ExampleWorker.
@HiltWorker
class ExampleWorker @AssistedInject constructor(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
Adding @AssistedInject
tells Hilt that some dependencies will be provided with a custom factory. We do not have to create our own factory because Hilt already provides one for us (HiltWorkerFactory
). HiltWorkerFactory will assist Hilt in injecting certain dependencies to ExampleWorker. The dependencies that HiltWorkerFactory should assist with are context
and workerParams
, and they must be annotated with @Assisted
.
@HiltWorker
class ExampleWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
For the dependencies that HiltWorkerFactory cannot help with, such as our custom class ExampleDependency, we can simply list them for Hilt to inject.
@HiltWorker
class ExampleWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
exampleDependency: ExampleDependency
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
Configuring Worker Initialization
Next, we will need to inject an instance of HiltWorkerFactory and modifies Worker initialization.
-
In MyApplication, implements
Configuration.Provider
.@HiltAndroidApp class MyApplication: Application(), Configuration.Provider { }
-
Inject an instance of HiltWorkerFactory into MyApplication with field injection.
@HiltAndroidApp class MyApplication: Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory }
-
Overrides
getWorkManagerConfiguration()
.@HiltAndroidApp class MyApplication: Application(), Configuration.Provider { @Inject lateinit var workerFactory: HiltWorkerFactory override fun getWorkManagerConfiguration() = Configuration.Builder() .setWorkerFactory(workerFactory) .build() }
-
Add the code below into the manifest, under
<application>
. This disables the default Worker initializer.<provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <meta-data android:name="androidx.work.WorkManagerInitializer" android:value="androidx.startup" tools:node="remove" /> </provider>
-
If your code shows a compile error because of the missing
tools
namespace, add thetools
namespace to your manifest.xmlns:tools="http://schemas.android.com/tools"
Run the App
We are now ready to run the app. Add some code to queue up your Worker in MainActivity’s onCreate()
to check if our Worker can be instantiated by Hilt.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val workRequest = OneTimeWorkRequestBuilder<ExampleWorker>().build()
WorkManager.getInstance(applicationContext).enqueue(workRequest)
}
After running the App, you should see a log entry similar to the one below, which means that our ExampleWorker has been created by Hilt successfully.
I/WM-WorkerWrapper: Worker result SUCCESS for Work [ id=84076b7b-5247-4d82-9ca4-7171e68c1aee, tags={ com.example.daniwebandroidnativeinjectworkerdepswithhilt.ExampleWorker } ]
Solution Code
MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val workRequest = OneTimeWorkRequestBuilder<ExampleWorker>().build()
WorkManager.getInstance(applicationContext).enqueue(workRequest)
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.daniwebandroidnativeinjectworkerdepswithhilt">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DaniwebAndroidNativeInjectWorkerDepsWithHilt">
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Project build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.41'
}
}
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Module **build.gradle**
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.daniwebandroidnativeinjectworkerdepswithhilt"
minSdk 21
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
//Work Manager
implementation 'androidx.work:work-runtime-ktx:2.7.1'
//Hilt
implementation 'com.google.dagger:hilt-android:2.41'
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'com.google.dagger:hilt-compiler:2.41'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
ExampleWorker.kt
@HiltWorker
class ExampleWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
exampleDependency: ExampleDependency
) : Worker(context, workerParams) {
override fun doWork(): Result {
return Result.success()
}
}
ExampleDependency.kt
class ExampleDependency @Inject constructor()
MyApplication.kt
@HiltAndroidApp
class MyApplication: Application(), Configuration.Provider {
@Inject lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
Summary
We have learned how to inject dependencies into a Worker in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebAndroidNativeInjectWorkerDepsWithHilt