Previous article in this series: Running Background Tasks in Android with WorkManager: Part 3
In this article, we will look at how to pass data to a task and how to get a result.
When we run a task, we may need to pass data to it and get back the result. Let's see how this can be done.
First, let's look at how to pass input data to the task:
val myData: Data = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.build()
val myWorkRequest: OneTimeWorkRequest = OneTimeWorkRequestBuilder<MyFirstWorker>()
.setInputData(myData)
.build()
WorkManager
.getInstance(this)
.enqueue(myWorkRequest)
The data is placed in the Data
object using its builder. Next, we pass this object to the setInputData
method of the WorkRequest builder.
When the task is running, then inside it (in MyFirstWorker
) we can receive input like this:
class MyFirstWorker(context: Context, parameters: WorkerParameters): Worker(context, parameters) {
private val TAG = this.javaClass.simpleName
override fun doWork(): Result {
val valueA = inputData.getString("keyA")
val valueB = inputData.getInt("keyB", 0)
Log.d(TAG, "doWork:valueA $valueA")
Log.d(TAG, "doWork:valueB $valueB")
return Result.success()
}
}
Logs:
2023-04-20 12:44:00 29725-29754 MyFirstWorker D doWork:valueA value1
2023-04-20 12:44:00 29725-29754 MyFirstWorker D doWork:valueB 1
For the task to return data, you must pass it to the setOutputData
method. The code in MyFirstWorker
would be:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyC", "value11")
.putInt("keyD", 11)
.build()
return Result.success(output)
}
We can get this output from WorkInfo
:
WorkManager
.getInstance(this)
.getWorkInfoByIdLiveData(myWorkRequest.id)
.observe(this) { info ->
when (info?.state) {
WorkInfo.State.FAILED -> {
}
WorkInfo.State.SUCCEEDED -> {
val valueC = info.outputData.getString("keyC")
val valueD = info.outputData.getInt("keyD", 0)
Log.e(TAG, "value: $valueC" )
Log.e(TAG, "value: $valueD" )
}
else -> {
}
}
}
Result:
2023-04-21 08:33:20 32749-32749 WorkManagerActivity E value: value11
2023-04-21 08:33:20 32749-32749 WorkManagerActivity E value: 11
The Data object that stores the data has a getKeyValueMap
method that will return to you a Map
containing all the information of this Data
.
And Data.Builder
has a putAll(Map<String, Object> values)
method into which you can pass a Map
, all the information which will be placed in Data
.
If you create a task sequence, the output of the previous task will be passed as input to the subsequent task.
For example, we run a sequence of the first and second tasks:
WorkManager
.getInstance(this)
.beginWith(myWorkRequest1)
.then(myWorkRequest2)
.enqueue()
If the first task returns output data like this:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.build()
return Result.success(output)
}
Then in the second, they will come as input and we can get them in the usual way:
override fun doWork(): Result {
val valueA = inputData.getString("keyA")
val valueB = inputData.getInt("keyB", 0)
Log.d(TAG, "doWork:valueA $valueA")
Log.d(TAG, "doWork:valueB $valueB")
return Result.success()
}
Logs:
2023-04-21 08:53:24 628-661 MySecondWorker D doWork:valueA value1
2023-04-21 08:53:24 628-661 MySecondWorker D doWork:valueB 1
Let's complicate the example a bit:
WorkManager
.getInstance(this)
.beginWith(listOf(myWorkRequest1, myWorkRequest2))
.then(myWorkRequest3)
.enqueue()
The first and second tasks are executed in parallel, then the third one is executed. As a result, the output from the first and second tasks will fall into the third. Let's see how it turns out.
Let the first task return the following data:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.putString("keyC", "valueC")
.build()
return Result.success(output)
}
And the second one is:
override fun doWork(): Result {
val output = Data.Builder()
.putString("keyA", "value2")
.putInt("keyB", 2)
.putString("keyD", "valueD")
.build()
return Result.success(output)
}
Please note that I specifically made the same keys: keyA
and keyB
in order to check which values of these keys will come to the third task - from the first task or from the second.
I output the input data of the third task to the log:
override fun doWork(): Result {
Log.d(TAG, "data " + inputData.keyValueMap)
return Result.success()
}
Result:
2023-04-21 09:04:02 783-838 MyThirdWorker D data {keyA=value2, keyB=2, keyC=valueC, keyD=valueD}
In the same keys (keyA and keyB), we see that the data came from the second task. At first, I thought that this happened because the second task took a little longer than the first, and it is logical that its values would overwrite the values from the first task when the keys matched. But then I ran that sequence again and got this result.
2023-04-21 09:05:47 963-995 MyThirdWorker D data {keyA=value1, keyB=1, keyC=valueC, keyD=valueD}
Now we see the values of the first task in the keys keyA
and keyB
.
If the tasks are executed in parallel, and if the keys match, it is not known from which task you will get the value. So be careful here.
To convert multiple outputs into a single input, use InputMerger
. There are several implementations for this, and the default is OverwritingInputMerger
. We have already seen how it works. If the key matches, then only one value will remain.
Consider another InputMerger - ArrayCreatingInputMerger
. If the keys match, it will create an array in which it will place all the values of this key.
Let's specify it for the third task using the setInputMerger
method:
val myWorkRequest3: OneTimeWorkRequest = OneTimeWorkRequestBuilder<MyThirdWorker>()
.setInputMerger(ArrayCreatingInputMerger::class.java)
.build()
The ArrayCreatingInputMerger
will now be used when merging the output from the previous tasks into the input of the third task.
The result of its work is always an array, even if there were no key matches:
override fun doWork(): Result {
val valueA = inputData.getStringArray("keyA")
val valueB = inputData.getIntArray("keyB")
val valueC = inputData.getStringArray("keyC")
val valueD = inputData.getStringArray("keyD")
Log.d(TAG, "valueA ${valueA?.toList()}")
Log.d(TAG, "valueB ${valueB?.toList()}")
Log.d(TAG, "valueC ${valueC?.toList()}")
Log.d(TAG, "valueD ${valueD?.toList()}")
return Result.success()
}
Let's use the same example to test it:
WorkManager
.getInstance(this)
.beginWith(listOf(myWorkRequest1, myWorkRequest2))
.then(myWorkRequest3)
.enqueue()
The first task will return the following data:
val output = Data.Builder()
.putString("keyA", "value1")
.putInt("keyB", 1)
.putString("keyC", "valueC")
.build()
and the second one is:
val output = Data.Builder()
.putString("keyA", "value2")
.putInt("keyB", 2)
.putString("keyD", "valueD")
.build()
In the third, we will receive the following input data:
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueA [value2, value1]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueB [2, 1]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueC [valueC]
2023-04-21 10:22:55 2307-2340 MyThirdWorker D valueD [valueD]
Now, when the keys match, the data is not overwritten but added to an array.
Also published here.