EditWorkManager method getWorkInfoByIdLiveData not giving the WorkInfo - android

I'm trying to get my WorkerInfo with the getWorkInfoByLiveData method that exists in the WorkManager instance.
val workInfo = workManager.getWorkInfoByIdLiveData(uuid).value
The WorkInfo is always null. Also, I'm calling this method from the main thread.
The scenario of how I'm checking this method. I try to enqueue my worker when a user sends a network request and if the internet is not connected I simply register a work with the WorkManager. After some time if I try to get the WorkerInfo with the UUID, it'll always give me null.
Note: When calling getWorkInfoByLiveData the Worker is not executed at that time.
Don't I'm expecting from WorkManager to give me WorkInfo with ENQUEUED State.
Edit 1:
So, another scenario would be like this, the app on which I'm working is like a social app. Now after registering a first worker, let's say the user don't want to see the posts from a specific user so this where I need to register my second worker because the user internet is not available at this time. Now what I need to do is to cancel the previously registered worker and then create a Chain of workers with not to show post of a user to beginWith and then the fetch all posts. Now in order to cancel the worker, I check that if the previous worker is still in Enqueued State then cancel it and create a new chain or workers.
Here is the code.
fun Context.isWorkerRegistered(uuid: UUID?): Boolean {
val id = uuid ?: return false
val workerInfo = workManager.getWorkInfoByIdLiveData(id).value
return workerInfo?.state == WorkInfo.State.ENQUEUED
}
The workInfo instance is always null.

Note: Livedata won't calculate the value until an active observer is added.
getWorkInfoByIdLiveData() returns a LiveData<WorkInfo> that you need to observe to get the workInfo value:
val status = workManager.getWorkInfoByIdLiveData(uuid)
.observe(this, Observer{ workInfo ->
if (workInfo!=null){
// ...
}
}
you can take a look at the WorkManager's codelab to see how it can be used.

Related

How to pass data from failed child worker to last worker in chain with android CoroutineWorkers

My current android application employs chained workers to perform complex background tasks.
Im observing only the last worker in the chain to detect whether or not the complete chain has completed successfully. This approach has been acceptable till now, as I have a new requirement where I need to also pass data when a worker fails.
is my only option to start observing all workers in the chain?
or
is it possible to set output data in a failing child worker and access it from the workinfo obtained from observing the last worker in the chain?
for example if i have a chain of 5 sequential workers as follows:-
WorkerA
WorkerB
WorkerC
WorkerD
WorkerZ
I am only observing WorkerZ, and WorkerC fails and sets outputData in its result, is there any way I can access that WorkData from observing only WorkerZ?
There is no need to observe all the workers. If you are returning data with Result.failure in each worker, you can easily find which worker failed and what was its output data like this:
WorkManager.getInstance(context)
.getWorkInfosForUniqueWorkLiveData(UNIQUE_WORK_NAME)
.observeForever { workInfos ->
if(it.state == WorkInfo.State.FAILED){ //failed worker
val failureResult = it.outputData
}
}
or like this:
val failureResult = WorkManager.getInstance(context)
.getWorkInfosForUniqueWork(UNIQUE_WORK_NAME)
.get()
.firstOrNull{
it.state == WorkInfo.State.FAILED
}
?.outputData

Parent and Child Coroutines not getting cancelled

I have a Parent Coroutine with a Child Coroutine like this:
val exceptionHandler = CoroutineExceptionHandler {_,e -> println("exception $e")}
val scope = CoroutineScope(Dispatchers.Main)
mainCoroutineJob = scope.launch(exceptionHandler){
val data = withContext(Dispatchers.IO){
val data = getData()
data
}
data?.let{
// do something with data
}
}
When I try to cancel both the parent and child coroutines using this:
mainCoroutineJob.cancel("Coroutine Cancelled")
mainCoroutineJob.cancelChildren(CancellationException("Coroutine Cancelled"))
the code inside withContext, keeps on running.
May I know why? And how can we cancel the withContext as well?
withContext is a suspend function, not a coroutine builder, therefore there's no child coroutine. The reason why it's not stopping when the job is cancelled is because it is not cooperative. You need to make your getData cooperatively cancellable. I assume it is already a suspend function, if so, then you just need to check at critical points whether the job that is running that suspend function still active and proceed only if it is. You can check it by using coroutineContext.isActive inside of a suspend function.
A critical point might be a loop or if there's no loop but a function is still doing some heavy processing you can divide your function into chunks and then before proceeding into processing the next chunk you check whether the suspend function's job is still active or not, and proceed only if it is active.

How to get WorkManager Status Synchronously

I am working with WorkManager Alpha 05.
I'm developing a Service that enqueues task on demand of other applications.
It has two methods:
createTask (Create a new task, given a name and a set of data, it returns and ID)
checkTaskStatus (The application asks the services given a ID, the status of the task)
The communication is done via bound services using messages. That means both client and services has the correct implementations to communicate information.
Method 1 is working fine.
I have problems with method 2.
WorkManager.getInstance().getStatusById(taskID)
.observe(LifecycleOwner, Observer {
status -> if (status !=null){
val myResult = status.state.toString()
statusString = myResult
Log.d("Task Status",myResult)
}
})
The observer is logging the status correctly, but I can't send back that message to the client. Is there a way to check the status in a sync way?
I don't really need to have the task attached to a LiveData.
Seems like SynchronousWorkManager was removed on October 11:
Removed WorkManager.synchronous() and WorkContinuation.synchronous() and all related methods. Added ListenableFuture as the return type of many methods in the API. This is a breaking API change.
How to use ListenableFuture:
You can now synchronously get and observe by using ListenableFutures. For example, WorkManager.enqueue() used to return void; it now returns a ListenableFuture. You can call ListenableFuture.addListener(Runnable, Executor) or ListenableFuture.get() to run code once the operation is complete.
More info can be found here.
The WorkManager instance has a synchronous method which returns the SynchronousWorkManager, This will give you a set of methods to perform synchronous operations. Take into account that this is meant to be used in a background thread.

How does WorkManager schedule GET requests to REST API?

I've had a look at the codelab for WorkManager plus some examples on here, but everything in code I have seen is either related to doing work locally on the device or work uploading to the server, not downloading data and responding to the data received. In the developer guidelines it even says, "For example, an app might need to download new resources from the network from time to time," so I thought it would be perfect for this task. My question is if WorkManager can handle the following scenario and if not, what is the proper tool for handling it:
Schedule a job that runs once a day in background
The job is to do a data fetch from the REST API (and post it to a LiveData object if possible).
When the data returns, check that it is newer than local data.
Notify the user that new data is available.
My worker class looks something like this:
public class MyWorker extends Worker {
#NonNull
#Override
public WorkerResult doWork() {
lookForNewData();
return WorkerResult.SUCCESS;
}
public void lookForNewData() {
MutableLiveData<MyObject> liveData = new MutableLiveData<>();
liveData.observe(lifeCycleOwner, results -> {
notifyOnNewData(results);
})
APILayer.getInstance().fetchData(searchParams, liveData)
}
My issue is of course that the LiveData object can't observe because there is no activity or fragment that can be its LifecycleOwner. But even if I used a callback from the API to respond to the data arriving, my worker would already have posted that it was successful and it probably would not proceed with the callback, right? So I kind of know this approach is totally wrong, but I can't see any code for getting data with WorkManager
Please help with a proper solution and some example code or some links, either with WorkManager if it can handle this kind of work or something else if it is more appropriate.
Schedule a job that runs once a day in background
You can schedule a PeriodicWorkRequest for that, which should be queued with enqueueUniquePeriodicWork. This makes sure only one PeriodicWorkRequest of a particular name can be active at a time.
Constraints constraint = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(MyWorker.class, 1, TimeUnit.DAYS)
.setConstraints(constraint)
.build();
WorkManager workManager = WorkManager.getInstance();
workManager.enqueueUniquePeriodicWork("my_unique_worker", ExistingPeriodicWorkPolicy.KEEP, workRequest);
The job is to do a data fetch from the REST API (and post it to a LiveData object if possible).
This can by done by sending your request synchronously within doWork() of your worker. I wouldn't use LiveData within your Worker class. We come to that later. The API call would look with Retrofit for example like that:
#Override
public WorkerResult doWork() {
Call<MyData> call = APILayer.getInstance().fetchData();
Response<MyData> response = call.execute();
if (response.code() == 200) {
MyData data = response.body();
// ...
} else {
return Result.RETRY;
}
// ...
return Result.SUCCESS;
}
When the data returns, check that it is newer than local data.
You fetched your API data in a synchronous way. Fetch your local data also synchronously and do whatever you need to do to compare them.
Notify the user that new data is available.
If you schedule a task with WorkManager it is guaranteed to run, even if your app is force-quit or the device is rebooted. So your task might complete while your app is not running. If you want to notify the user in any case you can send a notification. If you want to notify the user within a certain screen you can subscribe on your tasks status. For example like this (taken from the official guide):
WorkManager.getInstance().getStatusById(compressionWork.getId())
.observe(lifecycleOwner, workStatus -> {
// Do something with the status
if (workStatus != null && workStatus.getState().isFinished()) {
// ...
}
});
There's also getStatusesForUniqueWork(String uniqueWorkName) for our example.
The official guide is also explaining how to return data from you Task with which you can call setValue() on your MutableLiveData for example.
I would propose to update your local data within your Worker, subscribe on your workers status and once it succeeds update your UI with the local data (if you are not subscribed on your local data anyways, i.e. with Room and LiveData).
Edit: In reference to point 4, reading status of periodic work requests works a little different. They are only switching between ENQUEUED and RUNNING until CANCELLED. But will never have the state SUCCEEDED or FAILED. So listening for isFinished() might not be what you are expecting.
This is initial thought. Somebody please correct me if i'm wrong.
my worker would already have posted that it was successful and it probably would not proceed with the callback, right?
we can use the callback from API reponse, to construct output Data of the worker and set it using worker.setOutputData()
Then listen to the LiveData<WorkStatus> from workManager. From this workstatus we can get outputData using, workStatus.getOutputdata(). This data can give us the API response we want.
We can pass this response to next worker in the worker chain to carry out tasks like updating local DB.

Retrofit 2 with coroutine call adapter factory cancel request

I am trying to implement dynamic search functionality in my android application using retrofit2 with coroutine call adapter factory. When user type keyword and if keyword length is valid then app make request to server. In single request i can request like below
launch(UI) {
try {
val user = Client.provideService().getUsers()
//do sometihng with user.await()
}catch (e: Exception){
//Handle exception
}
}
but what if i want to cancel every previous request and make new request when user changes the editable ? I search a lot for an example but i cant find anything useful. Thanks for help.
If you want to cancel a coroutine, you can do that as explained in this guide. You need to invoke cancel on the Job that is returned from launch:
val job = launch {
//...
}
job.cancel() // cancels the job
But it's very important to know that Coroutine cancellation is cooperative, i.e. the block executed in the coroutine needs to react on the cancellation from outside the coroutine. You can check the state with isActive as described here.
As for your example, you would have to be able to cancel the computation of Client.provideService().getUsers() as soon as isActive becomes true.

Categories

Resources