Schedule a work on a specific time with WorkManager
Asked Answered
W

9

83

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.

Woodman answered 16/5, 2018 at 6:9 Comment(6)
OneTimeWorkRequest.Builder#setInitialDelay ?Clayton
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?Woodman
whats the problem with calculating timeInFutureInSeconds - timeNowInSeconds?Clayton
@Clayton 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.Oink
As the task is a periodic task, workmanager will trigger the task at the same time it was first triggered.Woodman
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 longevityCentuplicate
P
65

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.

Plaided answered 16/5, 2018 at 6:14 Comment(22)
Thank you for the response but won't this start the work immediately? I wanted to schedule it to start later.Woodman
Use setInitialDelay(long duration, TimeUnit timeUnit) in OneTimeWorkRequest.Builder Plaided
The problem is that it won't work for periodic works.Twopence
@Twopence you are right, its because for periodic build we dont need it.Plaided
@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.Twopence
@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.Plaided
I know, but this sounds a little bit overcomplicated.Twopence
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 workPlaided
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 :/Fracture
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...Fracture
@Fracture 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 mannerPlaided
@Plaided next thing, we can't use one time job for it, because: setInitialDelay is only for API 26 :D. We use now github.com/evernote/android-job , but I wanted to migrate from it to native solution, but OneTimeWorkRequest is useless without setInitialDelay for older platforms...Fracture
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.Maness
how is this answer accepted? the question states that we hae to execute the work at 8am. there is no mention of that?Oink
This is because you cannot schedule work with WorkManager on specific timePlaided
then what is the use of periodic work if i want to scedule it at 8pm everyday?Oink
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 usesCentuplicate
@Fracture I tried to use workermanager after 10 second so far I tried this .setInitialDelay(10, TimeUnit.SECONDS) but this never fired why ?Favian
@RajaSimon The minimum duration for delay is 15 minutes. You cannot and shouldn't trigger WorkManager so frequentlyPlaided
@Plaided 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);Favian
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.Favian
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.Inventory
B
22

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);
Bake answered 22/5, 2019 at 6:27 Comment(8)
I believe in the future the offset of execution time will be very large... But with REPLACE it seems to be okDelinda
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)Liberati
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)Liberati
@Delinda I believe in the future the offset of execution time will be very large can you please explain why?Carnallite
@Anjal why do you use ExistingPeriodicWorkPolicy.REPLACE?!Carnallite
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 ???Chongchoo
Edit: WorkManager alert 09:00 or 16:00 ???Chongchoo
If I want daily at 8 am hour, and the initial delay is 2 hours. Here the first time, it will run at 10 am, but what about the next day? is it going to be at 8 am or 10 am?Steib
B
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.

Bolitho answered 18/8, 2018 at 1:56 Comment(5)
Wow... that's pretty disgusting x.xRissole
I don't believe there's a reason to move from Evernote android-job thenRissole
Please take a lot at my answer here: #51945939Catechism
@Kerooker Actually, it is advised by one of the developers of WorkManager hereLiberati
@mhashim6, have some example ?Coincident
I
9

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);

}
Intrusion answered 12/9, 2019 at 19:25 Comment(3)
I tried it but often it does not work as expected, so it is better to use AlarmManagerHibernaculum
Certainly, if the time is to be exact developer.android.com/guide/backgroundIntrusion
now a days Alarm Manager not fired, for background purpose, and this answer is perfect,Coincident
F
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'
    }
Fidellia answered 16/11, 2018 at 7:18 Comment(0)
A
2

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);
Aeneous answered 18/5, 2019 at 10:17 Comment(0)
C
1

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

Catechism answered 29/1, 2019 at 14:21 Comment(0)
H
0

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

Hibernaculum answered 14/1, 2020 at 4:42 Comment(0)
C
0

I am using this method

private static Duration getRemainingDurationUntil(int hour) {
    // Get the current time
    LocalDateTime currentTime = LocalDateTime.now();

    // Add one day to the current time
    LocalDateTime nextDay = currentTime.plusDays(1);

    // Set the target time to 3 AM of the next day
    LocalDateTime targetTime = LocalDateTime.of(nextDay.toLocalDate(), LocalTime.of(hour, 0));

    // Calculate the duration between the current time and hour 
    Duration duration = Duration.between(currentTime, targetTime);

    // Get the remaining milliseconds
    //long remainingMilliseconds = duration.toMillis();
    return duration;
}

And

    Duration remainingDuration = getRemainingDurationUntil(REBIRTH_HOUR);

    // Create a PeriodicWorkRequest to run the task every XX minutes
    PeriodicWorkRequest periodicRebirthWorkRequest = new PeriodicWorkRequest.Builder(
            Rebirth.class,
            2, // Repeat interval
            TimeUnit.DAYS
    )
            .addTag(TAG_4_REBIRTH_WORK)
            .setInitialDelay(remainingDuration.toMinutes(),TimeUnit.MINUTES)
            .setConstraints(constraints) // Set the constraints (optional)
            .build();
Codel answered 6/3 at 21:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.