21

I have a Worker instance that needs to run every 24 hours which is pretty simple considering the PeriodicWorkRequest API. But here's the catch.

If the user initiates the work at say 8 PM, I need the first instance of the work manager to run at 9 AM the next morning and then follow the 24-hour periodic constraint.

I looked hereand I found that the OneTimeWorkRequest API has a setInitialDelay() function that can be used but I wasn't able to find anything for the PeriodicWork API.

Ther are some hacks for this such as I can use the OneTimeWork with the initial delay and then schedule a PeriodicWork from there but it's kinda a dirty hack.

Is there any way to do this with just the PeriodicWorkRequest API?

Sriram R
  • 2,109
  • 3
  • 23
  • 40

3 Answers3

16

On the new version of Work manager (Version 2.1.0-alpha02 released on May 16, 2019) PeriodicWorkRequests now support initial delays. You can use the setInitialDelay method on PeriodicWorkRequest.Builder to set an initial delay.

Example:

    PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(
        WorkerReminderPeriodic.class,
        24,
        TimeUnit.HOURS,
        PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS,
        TimeUnit.MILLISECONDS)
      .setInitialDelay(1, TimeUnit.HOURS)
      .addTag("send_reminder_periodic")
      .build();


    WorkManager.getInstance()
        .enqueueUniquePeriodicWork("send_reminder_periodic", ExistingPeriodicWorkPolicy.REPLACE, workRequest);
Anjal Saneen
  • 3,109
  • 23
  • 38
  • 3
    `setInitialDelay` works weird. When I set a startup period of 15 minutes and the initial delay of 1 minute, the worker starts after 16 minutes. I thought it would immediately (as `OneTimeWorkRequest`), but after 1 minute (and not after 16 minutes). And the subsequent starts will be every 15 minutes. – proninyaroslav Jul 23 '19 at 18:35
  • 4
    @proninyaroslav Periodic work has a minimum interval of 15 minutes and it cannot have an initial delay. MIN_PERIODIC_INTERVAL_MILLIS is 15 minute. https://developer.android.com/reference/kotlin/androidx/work/PeriodicWorkRequest#min_periodic_interval_millis – Anjal Saneen Jul 23 '19 at 19:36
  • Thank you. I will try to do a trick with `OneTimeWorkRequest` – proninyaroslav Jul 24 '19 at 09:29
  • 2
    Initial delay cannot be set on PeriodicWorker, initial delay is for oneTimeWorker only – SmartAndroidian Aug 01 '19 at 10:26
  • @SmartAndroidian Can you check the version of Work manager? it should be Version 2.1.0-alpha02+ to get the setInitialDelay(). – Anjal Saneen Aug 03 '19 at 14:54
  • 2
    I am using the version `implementation 'android.arch.work:work-runtime:1.0.1'` and here setInitialDelay() is not there. Sorry I am using stable version of `WorkManager` and not alpha. – SmartAndroidian Aug 05 '19 at 07:37
  • @SmartAndroidian You can use stable version `2.2.0-rc01`. it's there as well. – Anjal Saneen Aug 05 '19 at 17:02
  • A little clarification, this method (setInitialDelay) does not work for a worker on android 23, worker version 2.4.0 – Evgeny Gil Nov 08 '20 at 20:29
11

PeriodicWorkRequest has a nice Builder constructor where you can pass an interval for which the work can execute. You can check the details of the Builder and each parameter here

So, to set an initial delay to your periodic work you can do like this:

int hourOfTheDay = 10; // When to run the job
int repeatInterval = 1; // In days

long flexTime = calculateFlex(hourOfTheDay, repeatInterval);

Constraints myConstraints = new Constraints.Builder()
        .setRequiresBatteryNotLow(true)
        .build();

PeriodicWorkRequest workRequest =
        new PeriodicWorkRequest.Builder(MyNiceWorker.class,
                repeatInterval, TimeUnit.DAYS,
                flexTime, TimeUnit.MILLISECONDS)
                .setConstraints(myConstraints)
                .build();

WorkManager.getInstance().enqueueUniquePeriodicWork(YOUR_NICE_WORK_TAG,
        ExistingPeriodicWorkPolicy.REPLACE,
        workRequest);

Here is where the magic happens:

private long calculateFlex(int hourOfTheDay, int periodInDays) {

    // Initialize the calendar with today and the preferred time to run the job.
    Calendar cal1 = Calendar.getInstance();
    cal1.set(Calendar.HOUR_OF_DAY, hourOfTheDay);
    cal1.set(Calendar.MINUTE, 0);
    cal1.set(Calendar.SECOND, 0);

    // Initialize a calendar with now.
    Calendar cal2 = Calendar.getInstance();

    if (cal2.getTimeInMillis() < cal1.getTimeInMillis()) {
        // Add the worker periodicity.
        cal2.setTimeInMillis(cal2.getTimeInMillis() + TimeUnit.DAYS.toMillis(periodInDays));
    }

    long delta = (cal2.getTimeInMillis() - cal1.getTimeInMillis());

    return ((delta > PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS) ? delta
            : PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS);
}

Please be aware that this time is not exact. WorkManager may trigger your job at any time in that flex window depending on your constraints, system load, other scheduled jobs, etc.

If you want a precise timing, switch to AlarmManager.

Hope it helps!

Rafael Chagas
  • 774
  • 7
  • 13
4

With the current alpha release of WorkManager (v1.0.0-alpha07), i think it's not possible to set initial delay for the PeriodicWorkReqeust. May be we'll get some API in next releases.

For the time being, as you said, you can use a OneTimeWork request setup with initial delay, which will then en-queue a PeriodicWork request to WorkManager.

I would say it a hack but not that much dirty.

Qasim
  • 5,181
  • 4
  • 30
  • 51