Introduction
When working with WorkManager, it is important to know how to provide input data to your Workers. In this tutorial, we will learn how to provide basic input data to a Worker as well as when Workers are chained together.
Goals
At the end of the tutorial, you would have learned:
- How to provide input data to Workers.
Tools Required
- Android Studio. The version used in this tutorial is Bumblebee 2021.1.1 Patch 3.
Prerequisite Knowledge
- Intermedia Android.
- Basic WorkManager.
Project Setup
To follow along with the tutorial, perform the steps below:
-
Create a new Android project with the default Empty Activity.
-
Add the dependency to WorkManager library below into your Module build.gradle file.
//Kotlin Worker implementation "androidx.work:work-runtime-ktx:2.7.1"
-
Replace activity_main.xml with the code below. This adds a Button to start the Workers later in the tutorial.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button_startWorker" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/button_start_worker" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
-
Add the
<string>
resource below intostrings.xml
.<string name="button_start_worker">Start Worker</string>
-
Create a new class called DownloadWorker with the code below. This Worker does not do anything yet and simply returns
Result.sucess()
at this point.class DownloadWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) { override fun doWork(): Result { return Result.success() } }
-
In MainActivity, append the code below into the function
onCreate()
. This binds the Button’s OnClickListener to enqueue a Work request. In a real application, avoid using WorkManager directly in the UI because it actually belongs to the Data layer (Repository/Data Source).val button = findViewById<Button>(R.id.button_startWorker) val workManager = WorkManager.getInstance(applicationContext) val downloadWorkRequest = OneTimeWorkRequestBuilder<DownloadWorker>() .build() button.setOnClickListener { workManager.enqueue(downloadWorkRequest) }
Provide Input Data to DownloadWorker
Realistically, a DownloadWorker should have access to some kind of URL to load data. We did not provide it with any data, so let us do that now.
-
In
MainActivity#onCreate()
, after theworkManager
and before thedownloadWorkRequest
declarations, create a Data (androidx.work.Data
) object using itsBuilder()
.val data = Data.Builder() .putString(WORKER_INPUT_KEY_URL, "daniweb.com") .build()
-
WORKER_INPUT_KEY_URL
is not a pre-made key, we will have to create it ourselves by declaring it at the top level inDownloadWorker.kt
.const val WORKER_INPUT_KEY_URL = "0"
-
Add another step to the OneTimeWorkRequestBuilder building process to put the data that we just built, using
setInputData()
.val downloadWorkRequest = OneTimeWorkRequestBuilder<DownloadWorker>() .setInputData(data) .build()
-
Back at
DownloadWorker#doWork()
, access the input data using theinputData
property access, and then add logic to fail the Worker if the value isnull
.override fun doWork(): Result { val url = inputData.getString(WORKER_INPUT_KEY_URL) return if (url != null){ Result.success() } else { Result.failure() } }
-
We can now run the app, but do not click on the Button yet.
-
Click on the App Inspection tab in Android IDE -> Background Task Inspector. After clicking on the button, you should see that your DownloadWorker ran successfully.
Input Data to Chained Workers
Now we will learn how to use the inputData
when Workers are chained together. Create two more Workers called ProcessDataWorker
class ProcessDataWorker (appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams){
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success()
} else {
Result.failure()
}
}
}
and PostProcessWorker
class PostProcessWorker (appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams){
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success()
} else {
Result.failure()
}
}
}
Both of these workers will fail if url
is null
. Modify MainActivity#onCreate()
to call all three Workers in a chain like the code below.
val processDataWorkRequest = OneTimeWorkRequestBuilder<ProcessDataWorker>()
.build()
val postProcessDataWorker = OneTimeWorkRequestBuilder<PostProcessWorker>()
.build()
button.setOnClickListener {
workManager
.beginWith(downloadWorkRequest)
.then(processDataWorkRequest)
.then(postProcessDataWorker)
.enqueue()
}
After running the app and clicking on the Button, we can see the Workers failing.
ProcessDataWorker failed because url
is null, which also causes PostProcessWorker to fail. Apparently, the inputData
that we provided to the first Worker (DownloadWorker) is only valid in DownloadWorker, but not in other Workers in the chain.
To pass the data down the chain, we can use the overloaded version of Result.success()
, which optionally takes a Data object. Modify both doWork()
functions of DownloadWorker and ProcessDataWorker with the code below.
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success(inputData)
} else {
Result.failure()
}
}
Now, if we run the code again and press the Button, we can see that all Workers are completed successfully. This is because inputData
has been passed down the chain. You can also create a new Data object to pass down the chain if you wish.
Solution Code
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button_startWorker)
val workManager = WorkManager.getInstance(applicationContext)
val data = Data.Builder()
.putString(WORKER_INPUT_KEY_URL, "daniweb.com")
.build()
val downloadWorkRequest = OneTimeWorkRequestBuilder<DownloadWorker>()
.setInputData(data)
.build()
val processDataWorkRequest = OneTimeWorkRequestBuilder<ProcessDataWorker>()
.build()
val postProcessDataWorker = OneTimeWorkRequestBuilder<PostProcessWorker>()
.build()
button.setOnClickListener {
workManager
.beginWith(downloadWorkRequest)
.then(processDataWorkRequest)
.then(postProcessDataWorker)
.enqueue()
}
}
}
DownloadWorker.kt
const val WORKER_INPUT_KEY_URL = "0"
class DownloadWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success(inputData)
} else {
Result.failure()
}
}
}
ProcessDataWorker.kt
class ProcessDataWorker (appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams){
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success(inputData)
} else {
Result.failure()
}
}
}
PostProcessWorker.kt
class PostProcessWorker (appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams){
override fun doWork(): Result {
val url = inputData.getString(WORKER_INPUT_KEY_URL)
return if (url != null){
Result.success()
} else {
Result.failure()
}
}
}
activitiy_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button_startWorker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_start_worker"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Module build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.daniwebprovideinputdatatoworkers"
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 {
//Kotlin Worker
implementation "androidx.work:work-runtime-ktx:2.7.1"
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'
}
strings.xml
<resources>
<string name="app_name">Daniweb Provide Input Data To Workers</string>
<string name="button_start_worker">Start Worker</string>
</resources>
Summary
We have learned how to provide input data to Workers in this tutorial. The full project code can be found at https://github.com/dmitrilc/DaniwebProvideInputDataToWorkers.