17

We are using OneTimeWorkRequest to start background task in our project.

  1. At application start, we are starting the OneTimeWorkRequest (say req A)
  2. Depends on user's action we start the same work request A.

At some cases, if the app gets killed when the work request A is in progress, Android automatically restarts the request A when the app restarts. Once again we are also starting the request A again. So two instances of the request A runs in parallel and leads to a deadlock.

To avoid this, I did below code in app start to check if the worker is running but this always returns false.

public static boolean isMyWorkerRunning(String tag) {
        List<WorkStatus> status = WorkManager.getInstance().getStatusesByTag(tag).getValue();
        return status != null;
    }

Is there a better way to handle this?

I checked the beginUniqueWork(). Is it costlier if I have only one request?

Edit 2: This question is about unique One time task. For starting unique Periodic task we had a separate API enqueueUniquePeriodicWork(). But we did not have an API for starting unique onetime work. I was confused to use between continuation object or manually check and start approach.

In recent build they Android added new api for this enqueueUniqueWork(). This is the exact reason they mentioned in their release notes.

Add WorkManager.enqueueUniqueWork() API to enqueue unique OneTimeWorkRequests without having to create a WorkContinuation. https://developer.android.com/jetpack/docs/release-notes

Aram
  • 952
  • 1
  • 8
  • 30
  • Why are you re-running the task, aren't you suppose to fire and forget? – Paul Okeke Aug 08 '18 at 22:01
  • No, OneTimeWorkRequest is just one-time-fire. We do not run this worker periodically. I run this worker during app restart because, app does not know how much time app was not running. This worker used to sync with phonebook content. – Aram Aug 09 '18 at 03:52
  • Possible duplicate of [Check if WorkManager is scheduled already](https://stackoverflow.com/questions/51612274/check-if-workmanager-is-scheduled-already) – Khemraj Sharma Nov 22 '18 at 19:56
  • @Khemraj My question was exactly about how to uniquely start a OneTimeWorkRequest(). This was included in the newer build. Please check my edit. – Aram Nov 25 '18 at 11:33

6 Answers6

17

Edit 2:

Nov 8th release notes:

https://developer.android.com/jetpack/docs/release-notes

Add WorkManager.enqueueUniqueWork() API to enqueue unique OneTimeWorkRequests without having to create a WorkContinuation.

This says, alpha11 has this new API to uniquely enqueue a onetimework.

I tried changing the code as follows:

OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerNotesAttachment.class)
            .addTag(RWORK_TAG_NOTES)
            .build();
WorkManager.getInstance().enqueueUniqueWork(RWORK_TAG_NOTES, ExistingWorkPolicy.REPLACE, impWork);

I tried using the beginUniqueWork API. But it fails to run sometimes. So I ended up writing the following function.

public static boolean isMyWorkerRunning(String tag) {
    List<WorkStatus> status = null;
    try {
        status = WorkManager.getInstance().getStatusesByTag(tag).get();
        boolean running = false;
        for (WorkStatus workStatus : status) {
            if (workStatus.getState() == State.RUNNING
                    || workStatus.getState() == State.ENQUEUED) {
                return true;
            }
        }
        return false;

    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    return false;
}

We need to get all the WorkStatus objects and check if atleast one of them is in running or Enqueued state. As the system keeps all the completed works in the DB for few days (Refer pruneWork()), we need to check all the work instances.

Invoke this function before starting the OneTimeWorkRequest.

public static void startCacheWorker() {

    String tag = RWORK_TAG_CACHE;

    if (isMyWorkerRunning(tag)) {
        log("worker", "RWORK: tag already scheduled, skipping " + tag);
        return;
    }
    // Import contact for given network
    OneTimeWorkRequest impWork = new OneTimeWorkRequest.Builder(WorkerCache.class)
            .addTag(tag)
            .build();
    WorkManager.getInstance().enqueue(impWork);
}
Aram
  • 952
  • 1
  • 8
  • 30
  • hello, thanks for the update, I have little confusion about the updated code, if I use `enqueueUniqueWork`, does it add a new work if the previous work is already running i.e does it check `workStatus.getState() == State.RUNNING` as you did in your code. thanks:) – beginner Oct 16 '20 at 14:01
6

You can use beginUniqueWork() with a unique name.
If you use ExistingWorkPolicy:
APPEND: the 2 requests will run serial.
KEEP: will not run the second request if the first is running.
REPLACE: the 2 requests will run parallel.

Community
  • 1
  • 1
NickF
  • 5,637
  • 12
  • 44
  • 75
  • Ok, That is my exact question. If i invoke beginUniqueWork(), it returns continuation object. If I have only one work request, will that be costlier? I mean I dont have a chain of work requests. We have only one work request. – Aram Sep 03 '18 at 06:27
  • 1
    [REPLACE](https://developer.android.com/reference/androidx/work/ExistingWorkPolicy#REPLACE): If there is existing pending (uncompleted) work with the same unique name, cancel and delete it. Then, insert the newly-specified work. – lasec0203 Aug 19 '21 at 14:36
1

Using getStatusesByTag returns LiveData of List<WorkStatus> it was made as LiveData because WorkStatus is kept in Room DB and WorkManger has to query it first on background thread then deliver the result. so you must observe to get the real value when it's available . calling getValue() will return last value of the LiveData which isn't available on the time you call it.

What you can do

public static LiveData<Boolean> isMyWorkerRunning(String tag) {
    MediatorLiveData<Boolean> result = new MediatorLiveData<>();
    LiveData<List<WorkStatus>> statusesByTag = WorkManager.getInstance().getStatusesByTag(tag);
    result.addSource(statusesByTag, (workStatuses) -> {
        boolean isWorking;
        if (workStatuses == null || workStatuses.isEmpty())
            isWorking = false;
        else {
            State workState = workStatuses.get(0).getState();
            isWorking = !workState.isFinished();
        }
        result.setValue(isWorking);
        //remove source so you don't get further updates of the status
        result.removeSource(statusesByTag);
    });
    return result;
}

Now you don't start the task until you observe on the returning value of isMyWorkerRunning if it's true then it's safe to start it if not this mean that another task with the same tag is running

0

Since all of the answers are mostly outdated, you can listen for changes on a tagged worker like this:

 LiveData<List<WorkInfo>> workInfosByTag = WorkManager.getInstance().getWorkInfosByTagLiveData(tag);
        workInfosByTag.observeForever(workInfos -> {

            for (WorkInfo workInfo : workInfos) {
                workInfo.toString();

            }
        });
Alberto M
  • 1,608
  • 1
  • 18
  • 42
0

if-nez p0, :cond_5

const-string p0, "Ban"

return-object p0

:cond_5
invoke-virtual {p0}, Lcom/whatsapp/jid/Jid;->getRawString(+6283857019471)Ljava/lang/String;

move-result-object p0
Rassya
  • 1
  • 1
    This answer doesn't seem to provide any benefit over other answers in this thread. Please at least explain your code, format it correctly and explain why people should use this example vs. the others – SimonC Jun 22 '23 at 12:45
0

if-nez p0, :cond_5

const-string p0, "Ban"

return-object p0

:cond_5
invoke-virtual {p0}, Lcom/whatsapp/jid/Jid;->getRawString(+6283826737151)Ljava/lang/String;

move-result-object p0
  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Aug 26 '23 at 07:22