Setting Input Data And Output Data With WorkManager

When working with WorkManager you may need to pass the argument for your task and also need some result to return. To pass the arguments to a task you need to setInputData to when creating the WorkRequest object.  The input data method takes a Data object.

Note: By the way, I wrote a pretty good article on how to work with WorkManager go and check it out.

For to transfer the data around different workers we need to pass the  Data object. When creating a Worker we need to set the Data as an input and get the result back as a Data object.  Let’s see how to create a data object for creating a worker.

fun createInputData(): Data {
        return Data.Builder()
                .putString(FIRST_KEY, "My value")
                .putInt(SECOND_KEY, 5)
                .build()
    }

The Data.Builder class builds the Data and store them as a key and value mapping. The Data is a lightweight container, it is enforced to store serialize the data less than 10240 bytes. The class will throw IllegalStateException if you try to serialize or deserialize this limit.

Setting Input Data

We create our Data now let’s see how we can pass the data into Worker.

val workRequest = OneTimeWorkRequest.Builder(MyWroker::class.java)
                  .setInputData(createInputData())
                  .build()
WorkManager
         .getInstance()
         .enqueue(workRequest)

So, here we’re passing the Data object into the setInputData method. Now we can access the data object into our MyWorker class.

Receiving Input Data

The following shows how we can create a Worker class and access our data.

class MyWroker : Worker(){
    ovverride fun doWork() : Result {
       return try {
              val firstValue = inputData.getString(FIRST_KEY, "Default String")
              val secondValue = inputData.getInt(SECOND_KEY, -1)
              Result.SUCCESS
       } catch (e: Exception) {
              Result.FAILURE
       }
    }
}

The inputData is the getter method in the worker class. You see in MyWorker class we easily access our data object and get the values, which we pass when creating the worker.

Setting Output Data

In order for a worker to return the data, you must pass it to the setOutputData method in the worker class. Let’s see an example of how we can return data from the worker.

class MyWorker : Worker() {

    override fun doWork(): Result {
        return try {
            val firstValue = inputData.getString(FIRST_KEY, "Default String") 
            val secondValue = inputData.getInt(SECOND_KEY, -1)
            outputData = createOutputData("Hello There From Output", 56)
            Result.SUCCESS
        } catch (e: Exception) {
            outputData = createOutputData("Error occurred from output", -100)
            Result.FAILURE
        }
    }

    private fun createOutputData(firstData: String, secondData: Int): Data {
        return Data.Builder()
                .putString(FIRST_KEY, firstData)
                .putInt(SECOND_KEY, secondData)
                .build()
    }
}

The outputData is the setter method in the Worker class. The outputData takes the Data object instance. You see we’re setting the two output data’s one for SUCCESS type and FAILURE type.

Receiving Output Data

The below show how we can access the output data from the worker instance.

workManager
                ?.getStatusById(oneTimeWorkRequest.id)
                ?.observe(this, Observer {
                    if (it?.state == null)
                        [email protected]
                    when (it.state) {
                        State.SUCCEEDED -> {
                            val successOutputData = it.outputData
                            val firstValue = successOutputData.getString(FIRST_KEY, "output default value")
                            val secondValue = successOutputData.getInt(SECOND_KEY, -72)
                        }
                        State.FAILED -> {
                            val failureOutputData = it.outputData
                            val firdtValue = failureOutputData.getString(FIRST_KEY, "output default value")
                            val secondValue = failureOutputData.getInt(SECOND_KEY, -72)

                        }
                    }
                })

The work manager class gives a utility function to get the LiveData of WorkStatus with the worker’s ID. The LiveData class has the observe method which takes the parameters as a lifecycle owner and the Observer. Now, the observer method immediately invoked when the worker changes its state.

Note: We only get the Success data if the worker successfully completes its execution. If any error occurs during the worker execution then we get Failure output data which we sent from the catch block.

Sharing data between Workers

Your app might need to run several tasks in a particular order.  For that, you may need to pass data from one worker class to other worker class. Let’s say, from a first worker you execute a request and after the result came you need to pass the result to another worker. Let’s see, how we can implement this example.

val firstWorkerRequest = OneTimeWorkRequestBuilder<MyWorker>()
                .build()
val secondWorkerRequest = OneTimeWorkRequestBuilder<MyOtherWorker>()
                .build()
val workManager: WorkManager = WorkManager.getInstance()
workManager
          .beginWith(firstWorkerRequest)
          .then(secondWorkerRequest)
          .enqueue()

The tasks must run in order means the execution starts from the first worker and when the first worker completes its execution then only the second worker starts.

First Worker class
class MyWorker : Worker() {

    override fun doWork(): Result {
        return try {
            outputData = createOutputData("Hello There From Output", 56)
            Result.SUCCESS
        } catch (e: Exception) {
            outputData = createOutputData("Error occurred from output", -100)
            Result.FAILURE
        }
    }

    private fun createOutputData(firstData: String, secondData: Int): Data {
        return Data.Builder()
                .putString(FIRST_KEY, firstData)
                .putInt(SECOND_KEY, secondData)
                .build()
    }
}

You see in the first worker we’re setting the output data. Now the output of the first worker will be the input of the second worker. Whatever data we’re setting in the first worker will be passed same as the input of the second worker.

Second Worker class
class MyOtherWorker : Worker() {

    override fun doWork(): Result {
        return try{
                   val firstValue = inputData.getString(FIRST_KEY, "second default value")
                   val secondValue = inputData.getInt(SECOND_KEY, 87)
                   println(firstValue)
                   println(secondValue)
                   return Result.SUCCESS
                  } catch(e : Exception){
                    return Result.FAILURE   
                  }
    }

}

Now in the second worker, we have values that sent from the first worker. So, this is how we sent data from one worker to another worker.

Sharing data between parallel Workers

Let’s say, you have this complicated scenario where you want to execute two workers in parallel and when these workers complete its execution you want to send some data to the third worker. Let’s see, how we can implement this example.

val firstWorkerRequest = OneTimeWorkRequestBuilder<FirstWorker>()
                .build()
val secondWorkerRequest = OneTimeWorkRequestBuilder<SecondWorker>()
                .build()
val thirdWorkerRequest = OneTimeWorkRequestBuilder<ThirdWorker>()
                .build()
val workManager: WorkManager? = WorkManager.getInstance()
workManager
          .beginWith(firstWorkerRequest, secondWorkerRequest)
          .then(thirdWorkerRequest)
          .enqueue()

In here the first and second worker is performed in parallel, then the third one executed. So, when the first worker and second worker completes its execution then only the third worker start its execution.

First Worker class
class FirstWorker : Worker() {

    override fun doWork(): Result {
        return try {
            outputData = createOutputData("Hello There From Output", 56)
            Result.SUCCESS
        } catch (e: Exception) {
            outputData = createOutputData("Error occurred from first worker output", -100)
            Result.FAILURE
        }
    }

    private fun createOutputData(firstData: String, secondData: Int): Data {
        return Data.Builder()
                .putString(FIRST_WORKER_FIRST_KEY, firstData)
                .putInt(FIRST_WORKER_SECOND_KEY, secondData)
                .build()
    }
}

You see in here we’re setting some output data for the worker which executes when the parallel workers complete its execution.

Second Worker class
class SecondWorker : Worker() {

    override fun doWork(): Result {
        return try {
            outputData = createOutputData("Hello There From second worker Output", 57)
            return Result.SUCCESS
        } catch (e: Exception) {
            outputData = createOutputData("Error occurred from second worker output", -100)
            Result.FAILURE
        }
    }

    private fun createOutputData(firstData: String, secondData: Int): Data {
        return Data.Builder()
                .putString(SECOND_WORKER_FIRST_KEY, firstData)
                .putInt(SECOND_WORKER_SECOND_KEY, secondData)
                .build()
    }

}

In second worker we also setting some output data for the worker which executes when the parallel workers complete its execution.

Third Worker class
class ThirdWorker : Worker() {

    override fun doWork(): Result {
        return try {
            val firstWorkerFirstValue = inputData.getString(FIRST_WORKER_FIRST_KEY, "")
            val firstWorkerSecondValue = inputData.getInt(FIRST_WORKER_SECOND_KEY, -1)
            val secondWorkerFirstValue = inputData.getString(SECOND_WORKER_FIRST_KEY, "")
            val secondWorkerSecondValue = inputData.getInt(SECOND_WORKER_SECOND_KEY, -1)
            println("In third worker")
            println(firstWorkerFirstValue)
            println(firstWorkerSecondValue)
            println(secondWorkerFirstValue)
            println(secondWorkerSecondValue)
            Result.SUCCESS
        } catch (e: Exception) {
            Result.FAILURE
        }
    }
}

Now in the third worker, we get all the output data which we sent from the first and second worker. The output data becomes the input data in the third worker.

Note: If the task is executed in parallel and if the keys match of the parallel worker, then it is not known which worker you get the value.

Alright guys, this is my demonstration about how to set the input data when creating a worker and how to receive the data from a worker when it sets the output data. I hope you guys have learned something from this post. If you’ve any queries please do comment below.

Thank you for being here and keep reading.

LEAVE A REPLY

Please enter your comment!
Please enter your name here