Periodic work requests with WorkManager not working - android

I am using Workmanager to execute a task within a time period of minutes but it gets executed for the first time only. From my point of view it should execute every minutes.
I am testing on device while the app is in foreground running and power is on.
Code:
class MainActivity : AppCompatActivity() {
val TAG: String = "MainActivity"
lateinit var workLiveData: LiveData<List<WorkInfo>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initWM()
}
private fun initWM() {
val request = PeriodicWorkRequestBuilder<DemoWorker>(1, TimeUnit.MINUTES)
.addTag(TAG)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(TAG,
ExistingPeriodicWorkPolicy.REPLACE, request)
}
}
DemoWorker:
class DemoWorker(
context: Context,
params: WorkerParameters
) : Worker(context, params) {
val TAG: String = "MainActivity"
override fun doWork(): Result {
Log.d(TAG, "doWork: ")
return try {
Result.success(workDataOf("KEY" to "SUCCESS"))
} catch (e: Exception) {
Result.failure()
}
}
}

A reminder about the “minimal interval”. WorkManager is balancing two different requirements: the application with its WorkRequest, and the Android operating system with its need to limit battery consumption. For this reason, even if all the constraints set on a WorkRequest are satisfied, your Work can still be run with some additional delay.
So you are replacing one work after another. The OS may not have the proper time to execute the work. So the best option will be to try with a 1-hour delay.
You can use a flexInterval.Let’s look at an example. Imagine you want to build a periodic Work request with a 30 minutes period. You can specify a flexInterval, smaller than this period, say a 15 minute flexInterval.
The actual code to build a PeriodicWorkPequest with this parameters is:
val logBuilder = PeriodicWorkRequestBuilder<MyWorker>(
30, TimeUnit.MINUTES,
15, TimeUnit.MINUTES)
The result is that our worker will be executed in the second half of the period (the flexInterval is always positioned at the end of the repetition period):

Related

Are periodic work requests supposed to execute immediately?

EDIT (TL;DR)
I didn't realize there was more than one constructor for periodic work requests. The clue to my confusion was in the comments of the accepted answer.
Background
I have a few special cases I am trying to solve for while scheduling work. One of them involves doing work immediately and then creating a periodic work request. I found this in the Android's PeriodicWorkRequest documentation:
This work executes multiple times until it is cancelled, with the first execution happening immediately or as soon as the given Constraints are met.
I figured that this meant work would execute upon creating a request. However, this was not what happened in my test implementation. (For this work there is no need for a CoroutineWorker or network connection constraints but its applicable to my business need so I am testing it)
Starting Worker
object WorkerManager {
private val TAG = "WORKER_MANAGER_TEST"
fun buildWorkRequest(
startingNumber: Int,
context: Context
) {
val constraints =
Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val workRequest = PeriodicWorkRequest.Builder(
PeriodicWorker::class.java,
1,
TimeUnit.HOURS,
15,
TimeUnit.MINUTES
)
.setInputData(
workDataOf(Constants.INPUT_DATA_NUMBER to startingNumber)
)
.addTag(Constants.PERIODIC_WORKER_TAG)
.setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
Constants.PERIODIC_WORKER_NAME,
ExistingPeriodicWorkPolicy.REPLACE,
workRequest
)
Log.d(TAG, "Worker started. Starting number: $startingNumber")
}
}
Worker:
class PeriodicWorker(context: Context, workerParams: WorkerParameters): CoroutineWorker(context,
workerParams
) {
companion object {
var isInit = false
var count: Int = 1
}
override suspend fun doWork(): Result = try {
if (!isInit) {
count = inputData.getInt(Constants.INPUT_DATA_NUMBER, Constants.DEFAULT_DATA_NUMBER)
isInit = true
} else {
count += 1
}
Repository.updateNumber(count)
Result.success()
} catch (exception: Exception) {
Result.failure()
}
}
Repo:
object Repository {
private val TAG = "REPOSITORY_TAG"
private val _number = MutableStateFlow(0)
val number: StateFlow<Int> = _number
suspend fun updateNumber(number: Int) {
Log.d(TAG, "Number updated to: $number")
_number.emit(number)
}
}
ViewModel:
class NumberViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> = _count
init {
viewModelScope.launch {
Repository.number.collect {
_count.postValue(it)
}
}
}
}
Results
I started a worker with 10 as the starting number.
Logs:
8:45am - Worker started. Starting number: 10
9:37am - Number updated to: 10 // work executed
10:37am - Number updated to: 11 // work executed
11:37am - Number updated to: 12 // work executed
Device Info
OS Version 28 -- Samsung SM-T390
My Conclusion
Constraints -
Cannot be an issue. I had network connection during the above test and that is the only given constraint.
Battery Optimizations -
I am sure that this app was white listed prior to running this test.
So in conclusion it seems that PeriodicWorkRequests DO NOT perform immediate work. The Android documentation should instead say:
This work executes multiple times until it is cancelled, with the first period beginning immediately. The first work execution then happens within the first flex interval given the constraints are met.
Question
Does my conclusion seem reasonable? Is there something I haven't considered?
You are overthinking it. Please dump the JS:
https://developer.android.com/topic/libraries/architecture/workmanager/how-to/debugging
Use adb shell dumpsys jobscheduler
And just check what are the Unsatisfied constraints in the dump:
Required constraints: TIMING_DELAY CONNECTIVITY [0x90000000]
Satisfied constraints: DEVICE_NOT_DOZING BACKGROUND_NOT_RESTRICTED WITHIN_QUOTA [0x3400000]
Unsatisfied constraints: TIMING_DELAY CONNECTIVITY [0x90000000]
Also:
Minimum latency: +1h29m59s687ms
As I understand this constructor:
PeriodicWorkRequest.Builder(Class<? extends ListenableWorker> workerClass,
long repeatInterval, TimeUnit repeatIntervalTimeUnit,
long flexInterval, TimeUnit flexIntervalTimeUnit)
means that your work will be executed inside flexInterval of repeatInterval

JobScheduler - How to skip first job run of periodic job?

in my app i have set a periodic job that is set to run every 30 minutes.
The first job run occurs right when I do schedule that periodic job, which is not wanted in my case.
What I want is to skip the first run so that it will run for the first time after 30+ minutes.
My two thoughts on how to approach this was to either have it not run at all for the first 30 minutes somehow (some kind of delay), or mark the first job run as done before even having the chance to start.
Unfortunately I have not found any method in JobInfo that would allow me to do any of those.
Another workaround that would fulfill my needs would be to somehow limit the jobs to only occur while app is in the background. It does not entirely solve the issue but it could serve as a workaround in my case.
Following is my current code for scheduling the periodic job:
private void scheduleJob() {
ComponentName componentName = new ComponentName(this, myRecurringTask.class);
JobInfo info = new JobInfo.Builder(JOB_ID, componentName)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPersisted(true)
.setPeriodic(1800000)
.build();
JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
scheduler.schedule(info);
}
I hope someone has run into the same situation and can help me resolve it... Thank you!
Use WorkManager for scheduling backgound work, see introduction here.
1. Add Dependency:
implementation "androidx.work:work-runtime-ktx:2.4.0"
2. Create Worker Class:
class DataRefresher(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result { //will run on background thread
//your logic
return try {
//your logic
Result.success()
} catch (e: HttpException) {
Result.retry()
}
}
}
3. Create Application Class:
class DevBytesApplication : Application() {
private val backgroundScope = CoroutineScope(Dispatchers.Default) //standard background thread
override fun onCreate() { //called when app launches, same as Activity
super.onCreate()
initWork()
}
private fun initWork() {
backgroundScope.launch { //run in background, not affecting ui
setupDataRefreshingWork()
}
}
#SuppressLint("IdleBatteryChargingConstraints")
private fun setupDataRefreshingWork() {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) //when using wifi
.setRequiresBatteryNotLow(true)
.setRequiresCharging(true)
.setRequiresDeviceIdle(true) //when not running heavy task
.build()
val repeatingRequest = PeriodicWorkRequestBuilder<DataRefresher>(1, TimeUnit.DAYS) //【15 minutes is minimum!!】
.setConstraints(constraints)
.setInitialDelay(30, TimeUnit.MINUTES) //【initial delay!!】
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
DataRefresher::class.java.simpleName, //work name
ExistingPeriodicWorkPolicy.KEEP, //if new work comes in with same name, discard it
repeatingRequest
)
}
}
4. Setup AndroidManifest:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.devbytestest">
<application
android:name=".DevBytesApplication" //【here, must!!!】
...
</application>
</manifest>

Schedule task to be executed later (time + date) android

I just search for code that let me do an action after the timer (with specific date and time) finish (in Kotlin) and save it in a list
Like timer for post a tweet on Twitter:
https://business.twitter.com/en/help/campaign-editing-and-optimization/scheduled-tweets.html
You can use WorkManager for that.
Dependency:
implementation "androidx.work:work-runtime-ktx:2.3.0"
Example:
class LogWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here--in this case, upload the images.
Log.i("ToastWorker", "doWork: Working ⚒ ⚒ ⚒")
// Indicate whether the task finished successfully with the Result
return Result.success()
}
}
Then set the delay time
val logWorkRequest = OneTimeWorkRequestBuilder<LogWorker>()
.setInitialDelay(5, TimeUnit.SECONDS) // here you can set the delay time in Minutes, Hours
.build()
Start the timer
WorkManager.getInstance(this).enqueue(toastWorkRequest)
Here is a Codelab for more insight. You can also read more here

JobService does not repeat

In Android Oreo, I want to create a service that periodically updates data from the network.
class NetworkJobService : JobService() {
override fun onStopJob(p0: JobParameters?): Boolean {
jobFinished(p0,true)
return true
}
override fun onStartJob(p0: JobParameters?): Boolean {
//Average working time 3 to 5 minutes
NetworkConnect.connect()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doFinally {
jobFinished(p0,true)
}
.subscribe({result->
// Writes the parameters to the cache with the current time.
Cache.write("result : $result")
},{e->
// Writes the parameters to the cache with the current time.
Cache.write(e)
})
return true
}
}
This service is registered in the schedule when you run MainActivity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
jobSchedule()
button.setOnClickListener { readLog() }
}
val interval = 1000 * 60 * 15L
private fun jobSchedule(){
val jobScheduler = getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
val jobInfo = JobInfo.Builder(3,
ComponentName(this, NetworkJobService::class.java))
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(interval)
.build()
jobScheduler.schedule(jobInfo)
}
private fun readLog(){
//The log file is read line by line.
Cache.read()
.reversed()
.toObservable()
.subscribe({ text->
Log.i("Service Log",text)
},{
})
}
}
However, when I read the log file and checked the results, the service was running only when the MainActivity was running. In other words, it was not being rescheduled.
1) Run the activity and just turn off the device's screen
2) Run the activity and press the Home button to return to the launcher.
3) When the service is terminated and the app is deleted in the multitasking window
The most I wanted was to work in case 3), but in any of the above, the services I wanted were not rescheduled.
What have I missed?
While working with background threads in Oreo when the app is in killed you need to start the services as foreground service. Here is the details of the same. Essentially, show a notification to make the user aware that your app is trying to doing something in the background.
Hope it helps you.

Why use the OneTimeWorkRequest's setInitialDelay() function of the WorkManager, the delay does not apply when changing the device's time

I would like to use WorkManager to update the DB every 24 hours from midnight.
First, I'm understand that the Workmanager's PeriodicWorkRequest does not specify that the worker should operate at any given time.
So I used OneTimeWorkRequest() to give the delay and then put the PeriodicWorkRequest() in the queue, which runs every 24 hours.
1.Constraints
private fun getConstraints(): Constraints {
return Constraints.Builder()
.setRequiredNetworkType(NetworkType.NOT_REQUIRED)
.build()
}
2.OneTimeWorkRequest
fun applyMidnightWorker() {
val onTimeDailyWorker = OneTimeWorkRequest
.Builder(MidnightWorker::class.java)
.setInitialDelay(getDelayTime(), TimeUnit.SECONDS)
.setConstraints(getConstraints())
.build()
val workerContinuation =
workManager.beginUniqueWork(Const.DAILY_WORKER_TAG,
ExistingWorkPolicy.KEEP,
onTimeDailyWorker)
workerContinuation.enqueue()
}
getDelayTime() is
private fun getDelayTime(): Long {
...
return midNightTime - System.currentTimeMillis()
}
3.MidnightWorker Class
class MidnightWorker : Worker() {
override fun doWork(): Result {
DailyWorkerUtil.applyDailyWorker()
return Worker.Result.SUCCESS
}
}
4.PeriodicWorkRequest
fun applyDailyWorker() {
val periodicWorkRequest = PeriodicWorkRequest
.Builder(DailyWorker::class.java, 24, TimeUnit.HOURS)
.addTag(Const.DAILY_WORKER)
.setConstraints(getConstraints()).build()
workManager.enqueue(periodicWorkRequest)
}
And I confirmed that the delayTime passed and the midnightWorker was running.
Of course, It worked normally without any relation to the network.
However, test results showed that the delay time worked regardless of device time. As if it were server time
This is my question.
1. No delay according to device time. I want to know if the delay works as per the server time standard.
2. I wonder if the PeriodicWorkRequest can provide InitialDelay like OneTimeWorkRequest.
you've used TimeUnit.SECONDS with setInitialDelay yet getDelayTime is working with milliseconds.
This maybe the cause of you problems

Categories

Resources