80

WorkManager is a library used to enqueue work that is guaranteed to execute after its constraints are met.

Hence, After going though the Constraints class I haven't found any function to add time constraint on the work. For like example, I want to start a work to perform at 8:00am (The work can be any of two types OneTimeWorkRequest or PeriodicWorkRequest) in the morning. How can I add constraint to schedule this work with WorkManager.

S Haque
  • 6,881
  • 5
  • 29
  • 35
  • 4
    [OneTimeWorkRequest.Builder#setInitialDelay](https://developer.android.com/reference/androidx/work/OneTimeWorkRequest.Builder.html#setInitialDelay(long,%20java.util.concurrent.TimeUnit)) ? – pskink May 16 '18 at 06:14
  • I think, this can be a good way around of the problem. For this I have to calculate the time difference between the current time and the time I want the work to start. But can I set start time explicitly? – S Haque May 16 '18 at 07:15
  • whats the problem with calculating `timeInFutureInSeconds - timeNowInSeconds`? – pskink May 16 '18 at 07:17
  • @pskink but as the question asked how can we specify that the work has to be done everyday at 8am.? the initial delay is as specified the "initial" delay. how does the workmanager know the next time we need to run it. – Parth Anjaria Aug 01 '18 at 13:03
  • As the task is a periodic task, workmanager will trigger the task at the same time it was first triggered. – S Haque Aug 01 '18 at 16:32
  • WorkManager is specifically made to not support exact time use cases. Google has been moving away from supporting that, if you need it they want you to use push notifications. They want anything being scheduled from the client to be inexact to support user's battery longevity – davkutalek Aug 17 '18 at 17:46

8 Answers8

63

Unfortunately, you cannot schedule a work at specific time as of now. If you have time critical implementation then you should use AlarmManager to set alarm that can fire while in Doze to by using setAndAllowWhileIdle() or setExactAndAllowWhileIdle().

You can schedule a work, with onetime initial delay or execute it periodically, using the WorkManager as follows:

Create Worker class:

public class MyWorker extends Worker {
    @Override
    public Worker.WorkerResult doWork() {

        // Do the work here

        // Indicate success or failure with your return value:
        return WorkerResult.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

Then schedule OneTimeWorkRequest as follows:

OneTimeWorkRequest mywork=
        new OneTimeWorkRequest.Builder(MyWorker.class)
        .setInitialDelay(<duration>, <TimeUnit>)// Use this when you want to add initial delay or schedule initial work to `OneTimeWorkRequest` e.g. setInitialDelay(2, TimeUnit.HOURS)
        .build();
WorkManager.getInstance().enqueue(mywork);

You can setup additional constraints as follows:

// Create a Constraints that defines when the task should run
Constraints myConstraints = new Constraints.Builder()
    .setRequiresDeviceIdle(true)
    .setRequiresCharging(true)
    // Many other constraints are available, see the
    // Constraints.Builder reference
     .build();

Then create a OneTimeWorkRequest that uses those constraints

OneTimeWorkRequest mywork=
                new OneTimeWorkRequest.Builder(MyWorker.class)
     .setConstraints(myConstraints)
     .build();
WorkManager.getInstance().enqueue(mywork);

PeriodicWorkRequest can be created as follows:

 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder(MyWorker.class, 12, TimeUnit.HOURS)
                                   .build();
  WorkManager.getInstance().enqueue(periodicWork);

This creates a PeriodicWorkRequest to run periodically once every 12 hours.

Sagar
  • 23,903
  • 4
  • 62
  • 62
  • Thank you for the response but won't this start the work immediately? I wanted to schedule it to start later. – S Haque May 16 '18 at 07:12
  • Use `setInitialDelay(long duration, TimeUnit timeUnit)` in `OneTimeWorkRequest.Builder ` – Sagar May 16 '18 at 07:16
  • 6
    The problem is that it won't work for periodic works. – Gaket May 16 '18 at 17:36
  • @Gaket you are right, its because for periodic build we dont need it. – Sagar May 16 '18 at 21:58
  • @Sagar, you mean you don't needed it in your project or overall? I would like to be able to schedule a work that will happen at some particular hour daily. For example, if it is 2pm now I'd like to schedule a work that will start in 2 hours and will be repeated then. – Gaket May 16 '18 at 23:30
  • 2
    @Gatek, its overall. Its being mentioned in Google IO. If you want to do that then Schedule OneTimeWorkRequest which will trigger after 2hrs and PeriodicWorkRequest which will be executed at 2PM. – Sagar May 16 '18 at 23:34
  • I know, but this sounds a little bit overcomplicated. – Gaket May 16 '18 at 23:48
  • Well it depends on the perspective. From my point of view its cleaner approach. There is clear segregation between work to be done for 1 time and recurring work – Sagar May 17 '18 at 02:12
  • It's sooo complicated. We really need to plan periodic jobs at specific time. For example every day at 8 PM. But who cares, that now is 14.58 PM, I just need plan every day at 8, not to calculate time between NOW and 8 PM and run onetime job with delay and at 8 PM reschedule periodic job for 1 day :/ – mtrakal Jul 11 '18 at 12:59
  • When you must do the initial onetimejob, you don't need periodic jobs at all, because you can every period reschedule next one time job... – mtrakal Jul 11 '18 at 13:09
  • @mtrakal well you can do that, its just design choice. Google has provided a way to achieve periodic work execution however its not the only way to achieve it. It will be cleaner to do it one time using periodic job instead of doing it in nested manner – Sagar Jul 11 '18 at 13:22
  • @Sagar next thing, we can't use one time job for it, because: `setInitialDelay` is only for API 26 :D. We use now https://github.com/evernote/android-job , but I wanted to migrate from it to native solution, but `OneTimeWorkRequest` is useless without `setInitialDelay` for older platforms... – mtrakal Jul 11 '18 at 13:42
  • 4
    There are two methods setInitialDelay, one which expect a Duration as parameter, only in API 26 (because it comes from Java 8), the other which takes a duration and a timeunit, available on every platform. – NitroG42 Jul 13 '18 at 08:20
  • 46
    how is this answer accepted? the question states that we hae to execute the work at 8am. there is no mention of that? – Parth Anjaria Aug 01 '18 at 11:47
  • 3
    This is because you cannot schedule work with WorkManager on specific time – Sagar Aug 01 '18 at 11:48
  • 1
    then what is the use of periodic work if i want to scedule it at 8pm everyday? – Parth Anjaria Aug 01 '18 at 13:10
  • I think Google considers having to schedule all clients at the same time an edge case: it's not generally good design because e.g. you can easily overload your server if they are network requests. Plus you will also have time zone issues. In general they recommend using push notifications for exact time uses – davkutalek Aug 17 '18 at 17:43
  • @mtrakal I tried to use workermanager after 10 second so far I tried this `.setInitialDelay(10, TimeUnit.SECONDS)` but this never fired why ? – Raja Simon Aug 30 '18 at 08:47
  • @RajaSimon The minimum duration for delay is 15 minutes. You cannot and shouldn't trigger WorkManager so frequently – Sagar Aug 30 '18 at 09:30
  • @Sagar Thanks but where is this document? I tried to add delay for 15 mins but no success. I'm using this right now and it's not working. `WorkManager.getInstance().beginUniqueWork(workTag, ExistingWorkPolicy.REPLACE, notificationWork);` – Raja Simon Sep 01 '18 at 12:55
  • I know it replace the existing one and I am aware of the 15 mins window still when my app close it's not trigger the notification. – Raja Simon Sep 01 '18 at 12:57
  • 1
    In any backend, you could just specify a CRON trigger and it would execute periodically at that given time. Having to manually enqueue a job that executes the job that schedules a periodic job that may or may not work is nonsensical in comparison, and an API flaw of WorkManager. – EpicPandaForce Mar 10 '20 at 11:20
19

PeriodicWorkRequests now support initial delays from the version 2.1.0-alpha02. You can use the setInitialDelay method on PeriodicWorkRequest.Builder to set an initial delay. Link Here

Example of schedule at every day at 8:00 am. here I'm using joda time library for time operations.

final int SELF_REMINDER_HOUR = 8;

    if (DateTime.now().getHourOfDay() < SELF_REMINDER_HOUR) {
        delay = new Duration(DateTime.now() , DateTime.now().withTimeAtStartOfDay().plusHours(SELF_REMINDER_HOUR)).getStandardMinutes();
    } else {
        delay = new Duration(DateTime.now() , DateTime.now().withTimeAtStartOfDay().plusDays(1).plusHours(SELF_REMINDER_HOUR)).getStandardMinutes();
    }


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


    WorkManager.getInstance()
        .enqueueUniquePeriodicWork("send_reminder_periodic", ExistingPeriodicWorkPolicy.REPLACE, workRequest);
Anjal Saneen
  • 3,109
  • 23
  • 38
  • I believe in the future the offset of execution time will be very large... But with `REPLACE` it seems to be ok – Vlad Oct 04 '19 at 07:21
  • For me it will fire the periodic worker at the 1st time only at the end of 24hr period. Any way to make it work right after creation? (e.g. should run at 17:50, enqueued at 17:40 and runs after 10 minutes for the 1st time) – Vadim Kotov Jan 15 '20 at 15:04
  • Also, if user changes his time settings, workers will be intact (e.g. the worker should run every day at 8:00 am. If user sets another timezone, +3h more for example, it will not run at 11:00 am) – Vadim Kotov Jan 16 '20 at 12:17
  • @Vlad `I believe in the future the offset of execution time will be very large` can you please explain why? – Soheil Nov 02 '21 at 11:04
  • @Anjal why do you use `ExistingPeriodicWorkPolicy.REPLACE`?! – Soheil Nov 02 '21 at 11:07
  • Ok. when I set PeriodicWorkRequest at time 01:00 so WorkManager alert at 09:00. when I change time 01:00 to 08:00 (+7Hours), WorkManager alert 09:00 or 17:00 ??? – A. Sang Feb 04 '23 at 07:00
  • Edit: WorkManager alert 09:00 or 16:00 ??? – A. Sang Feb 04 '23 at 08:20
16

So far it's not possible to achieve exact times using PeriodicWorkRequest.
An ugly work-around would be using a OneTimeWorkRequest and when it fires, set another OneTimeWorkRequest with a new calculated period, and so on.

mhashim6
  • 527
  • 6
  • 19
4

I might be somewhat late but anyway I did this in order to schedule a WorkRequest at a given time (with an optional short delay). You just need to get the time from a TimePicker of whatever:

public static void scheduleWork(int hour, int minute) {
    Calendar calendar = Calendar.getInstance();
    long nowMillis = calendar.getTimeInMillis();

    calendar.set(Calendar.HOUR_OF_DAY,hour);
    calendar.set(Calendar.MINUTE,minute);
    calendar.set(Calendar.SECOND,0);
    calendar.set(Calendar.MILLISECOND,0);

    if (calendar.before(Calendar.getInstance())) {
        calendar.add(Calendar.DATE, 1);
    }

    long diff = calendar.getTimeInMillis() - nowMillis;

    WorkManager mWorkManager = WorkManager.getInstance();
    Constraints constraints = new Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build();
    mWorkManager.cancelAllWorkByTag(WORK_TAG);
    OneTimeWorkRequest mRequest = new OneTimeWorkRequest.Builder(NotificationWorker.class)
            .setConstraints(constraints)
            .setInitialDelay(diff,TimeUnit.MILLISECONDS)
            .addTag(WORK_TAG)
            .build();
    mWorkManager.enqueue(mRequest);

}
Tricky Bay
  • 796
  • 9
  • 20
Ignacio Garcia
  • 973
  • 9
  • 9
3

All answers are now obsolete, upgrade to WorkManager 2.1.0-alpha02 (or beyond) setInitialDelay() method used to work only for OneTimeWorkRequest, but now they also support PeriodicWorkRequest.

implementation "androidx.work:work-runtime:2.1.0-alpha02"

PeriodicWorkRequests now support initial delays. You can use the setInitialDelay method on PeriodicWorkRequest.Builder to set an initial delay

quick example:

new PeriodicWorkRequest.Builder(MyWorker.class, MY_REPEATS, TimeUnit.HOURS)
        .setInitialDelay(THE_DELAY,TimeUnit.SECONDS);
DCD
  • 325
  • 6
  • 25
usernotnull
  • 3,480
  • 4
  • 25
  • 40
2

You can use AndroidJob from evernote

class NotificationJob : DailyJob() {

override fun onRunDailyJob(params: Params): DailyJobResult {
       //your job       
       return DailyJobResult.SUCCESS

}

companion object {
    val TAG = "NotificationJob"
    fun scheduleJob() {
        //scheduled between 9-10 am

        DailyJob.schedule(JobRequest.Builder(TAG), 
            TimeUnit.HOURS.toMillis(9),TimeUnit.HOURS.toMillis(10 ))
     }
   }
 }

and a notification creator

  class NotificationJobCreator : JobCreator {

        override fun create(tag: String): Job? {
           return when (tag) {
              NotificationJob.TAG ->
                NotificationJob()
              else ->
                null
           }
       }
  }

then initiate in your Application class

    JobManager.create(this).addJobCreator(NotificationJobCreator())

The gradle dependency is

    dependencies {
        implementation 'com.evernote:android-job:1.2.6'

        // or this with workmnager 
        implementation 'com.evernote:android-job:1.3.0-alpha08'
    }
Uzair
  • 1,529
  • 14
  • 17
0

If you want to set an initial delay to a PeriodicWorkRequest, I presented the solution here:

Set initial delay to a Periodic Work Manager in Android

Rafael Chagas
  • 774
  • 7
  • 13
0

I tried OneTimeWorkRequest but it is flaky(work only sometimes) so we should not rely on it. AlarmManager is a better option.

Nurseyit Tursunkulov
  • 8,012
  • 12
  • 44
  • 78