I have such code, I need to implement the task queue, if the task is in the queue, then you do not need to add it.
I implemented as shown in when, everything works, but sometimes the state of the worker remains ENQUEUED, and new tasks are not added to the queue.
That is, when there is no Internet, I add a task, when the Internet appears, tasks begin to run out, but for some reason, sometimes it doesn’t happen, I can’t understand why the task does not start despite the fact that the Internet is there and the task is in the queue.
How can you determine why the task will not start?
Does anyone have a better suggestion?
//run task
runOneTimeWorkByType<GetDocumentsWorker>(GET_DOCUMENTS_TAG)
private inline fun <reified W : Worker> runOneTimeWorkByType(tag: String) {
val workerInfoList = workManager
.getWorkInfosByTag(tag)
.get()
for (item in workerInfoList) {
if (item.state == WorkInfo.State.ENQUEUED){
return
}
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest =
OneTimeWorkRequestBuilder<W>()
.setConstraints(constraints)
.addTag(tag)
.build()
workManager.enqueue(workRequest)
}
class GetDocumentsWorker(ctx: Context, workerParams: WorkerParameters) :
Worker(ctx, workerParams) {
#Inject
lateinit var serviceUtils: ServiceUtils
init {
App.appComponent.inject(this)
}
override fun doWork(): Result {
Log.d("workmng", "GetDocumentsWorker: start")
try {
serviceUtils.documentsGet()
} catch (e: Exception) {
Log.d("workmng", "GetDocumentsWorker: exception", e.cause)
return Result.retry()
}
Log.d("workmng", "GetDocumentsWorker: end")
return Result.success()
}
}
UPDATE:
I tried to start the task without conditions, but in this case, nothing starts either, have ideas why so?
fun runGetDocumentsTask() {
val workRequest =
OneTimeWorkRequestBuilder<GetDocumentsWorker>()
.addTag(GET_DOCUMENTS_TAG)
.build()
workManager.enqueue(workRequest)
}
Everything starts to work fine when I cancel jobs: workManager.cancelAllWork()
When creating a worker, I run several periodic tasks, can there be a problem in them? If so, how to fix it?
private var workManager: WorkManager = WorkManager.getInstance(ctx)
init {
//workManager.cancelAllWork()
runSendAllPeriodicTasks()
}
private fun runSendAllPeriodicTasks() {
runOneTimeWorkOnPeriod<SendAllWorker>(15, TimeUnit.MINUTES)
runOneTimeWorkOnPeriod<FailureFilesResendWorker>(3, TimeUnit.HOURS)
runOneTimeWorkOnPeriod<GetItemsWorker>(1, TimeUnit.HOURS)
}
Related
I am trying out Android WorkManager which is successfully being triggered after every 15 minutes.
However, the work is not being done and I get this error on my logs.
I/WM-WorkerWrapper: Worker result FAILURE for Work
This is how I have set-up my Constraints (Inside the Application Class) to trigger the work.
//set-up work
private fun setUpAsteroidLoadingWork() {
//define work constraints
val workConstraints =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresCharging(false)
.build()
//create WorkRequest
val workRequest = PeriodicWorkRequestBuilder<LoadAsteroidsWorker>(15, TimeUnit.MINUTES)
.setConstraints(
workConstraints)
.build()
//get WorkManager
val workManager = WorkManager.getInstance(this)
//enqueue work
workManager.enqueueUniquePeriodicWork(
LoadAsteroidsWorker.WORK_NAME, ExistingPeriodicWorkPolicy.KEEP, workRequest)
}
I start the work inside the Application Class onCreate() method
override fun onCreate() {
super.onCreate()
//initialize Timber
Timber.plant(Timber.DebugTree())
Timber.i("Application's onCreate Called")
//start work inside onCreate
runWorkInBackground()
}
//switch work to run on background
private fun runWorkInBackground(){
CoroutineScope(Default).launch {
setUpAsteroidLoadingWork()
}
}
The code is supposed to trigger some work to download internet data in the repository. I have run a #GET request on postman and data is returned with no errors.
This is the Worker Class
class LoadAsteroidsWorker(context: Context, params: WorkerParameters) :
CoroutineWorker(context, params) {
companion object {
const val WORK_NAME = "LoadAsteroidWorker"
}
override suspend fun doWork(): Result {
Timber.i("do workWork() called")
//get instance of database for use with Repo initialization below
val db = AsteroidDatabase.getDatabaseInstance(applicationContext)
//initialize Repo
val repo = AsteroidRepo(db)
return try {
//define work i.e. load asteroids from Network for the next seven days
repo.getAsteroidsFromNetwork()
Timber.i("called repo method")
Result.success()
}catch (e:HttpException){
Timber.i("error - $e")
Result.retry()
}
}
}
This my WorkManager Dependency
//WorkManager - Kotlin + coroutines implementation 'androidx.work:work-runtime-ktx:2.6.0-alpha02'
Any leads on what I am doing wrong?
After a lot of searching, it has dawned on me that the issue is on the doWork() method where I was only 'catching' HttpException forgetting that there are other exceptions to deal with.
I added a second catch block which at last caught the 'bug'.
override suspend fun doWork(): Result {
Timber.i("do workWork() called")
//get instance of database for use with Repo initialization below
val db = AsteroidDatabase.getDatabaseInstance(applicationContext)
//initialize Repo
val repo = AsteroidRepo(db)
return try {
//define work i.e. load asteroids from Network for the next seven days
repo.getAsteroidsFromNetwork()
Timber.i("called repo method")
Result.success()
}catch (e:HttpException){
Timber.i("error - $e")
Result.retry()
}catch (e: Exception){
//catch general exceptions here
Timber.i("exception - $e")
Result.failure()
}
}
The issue had nothing to do with WorkManager but a JsonDataException.
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>
I'm using WorkManager API (version 2.4.0) to create simple periodic running tasks. Here is the worker class
class BackupWorker(
context: Context,
workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result = coroutineScope {
Log.i(TAG, "Starting worker")
makeNotification(
"Preparing worker",
applicationContext
)
sleep() //simulate long running task
for(i in 0..20) {
makeNotification(
"Firing update $i",
applicationContext,
true
)
sleep()
}
makeNotification(
"Worker complete",
applicationContext
)
Log.i(TAG, "Finishing backup worker")
Result.success()
}
}
And the work request is set up as follows
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresStorageNotLow(true).build();
PeriodicWorkRequest workRequest =
new PeriodicWorkRequest.Builder(BackupWorker.class,
15, TimeUnit.MINUTES)
.setConstraints(constraints)
.build();
WorkManager.getInstance(context).enqueueUniquePeriodicWork("tag_worker",
ExistingPeriodicWorkPolicy.REPLACE,
workRequest);
The work request is being correctly picked up by the OS. However, if I turn Wi-Fi off whilst running, it doesn't stop and continues to run what's inside doWork() which in fact contradicts the whole purpose of this API.
Is there anything missing here? Any thoughts?
I am developing an Android app using the WorkManager (Android Jetpack) with Rx.
Below is the Worker class.
class ImageRxWorker(
appContext: Context,
private val workerParams: WorkerParameters
) : RxWorker(appContext, workerParams) {
override fun createWork(): Single<Result> = Single.create<Result> { emitter -
// do the job
emitter.onSuccess(Result.success())
}
}
It works fine, there is no problem.
But what I want to know is how can I handle the result?
class MainPresenter(
private val view: MainActivity,
private val workManager: WorkManager = WorkManager.getInstance()
) : MainContract.Presenter {
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
val uploadWorkRequest = OneTimeWorkRequestBuilder<ImageRxWorker>().build()
workManager.enqueue(uploadWorkRequest)
emitter.onComplete() // This is not exit immediately.
}
}
}
I found "addListener", "result", but I don't know how to use them.
And I tried to googling but I cannot find any good reference.
Somebody help me!
I think... I found one of the solutions.
It WORKS!!!
But... it is... very ugly... and not smart...
(In my app, I don't use LiveData.)
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
Log.d(TAG, "[WM][Presenter] startWork - start")
val workRequest = OneTimeWorkRequestBuilder<ImageRxWorker>()
.setInputData(workDataOf("TIME" to 1000L))
.build()
workManager.enqueue(workRequest)
while (workManager.getWorkInfoById(workRequest.id).get().state != WorkInfo.State.SUCCEEDED) {
// Should I really polling?
Thread.sleep(1000)
Log.d(TAG, "[WM][Presenter] not yet......")
}
Log.d(TAG, "[WM][Presenter] complete")
emitter.onComplete()
}
}
Wow, here is the third code that was written by "User One"'s answer.
It works fine and looks better than the second code.
Because my app doesn't use 'LiveData', I cannot ensure that whether this code is valid.
In the "observeForever", I am calling "cancelWorkById" after the Worker is done.
Is it correct?
override fun startWork(): Completable {
view.showToastMessage(R.string.worker_started)
return Completable.create { emitter ->
Log.d(TAG, "[WM][Presenter] startWork - start")
val workRequest = OneTimeWorkRequestBuilder<ImageRxWorker>()
.setInputData(workDataOf("TIME" to 1000L))
.build()
workManager.enqueue(workRequest)
workManager.getWorkInfoByIdLiveData(workRequest.id).observeForever { workInfo ->
workInfo?.outputData?.getString("key")?.let { data ->
Log.d(TAG, "[WM][Presenter] startWork - complete: $data")
emitter.onComplete()
workManager.cancelWorkById(workRequest.id)
}
}
}
}
The Method you use getWorkInfoById return a ListenableFuture, and this one return a LiveData :
https://developer.android.com/reference/androidx/work/WorkManager.html#getWorkInfoByIdLiveData(java.util.UUID)
Instead of your while loop, You can simply observe The Work Status by observing the LiveData returned by getWorkInfoByIdLiveData() and then call emitter.onComplete() once it's trigerred, but you have no LifeCycle here in your presenter so you should use observeForever() and take care of removing the Observer,
Here is an example :
workManager.getWorkInfoByIdLiveData(workRequest.id)
.observeForever(object : Observer<WorkInfo> {
override fun onChanged(workInfo : WorkInfo?) {
if(workInfo.state == WorkInfo.State.SUCCEEDED) {
////The Work result is a Success
}
/* Here We remove the Observer if Not needed anymore
'this' here = the Observer */
workManager.getWorkInfoByIdLiveData(workRequest.id)
.removeObserver(this)
}
Or simply use the ListenableFuture returned by getWorkInfoById() to get a CallBack
I have the following one-time worker.
// Create a Constraints that defines when the task should run
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED)
.setRequiresBatteryNotLow(true)
// Many other constraints are available, see the
// Constraints.Builder reference
.build();
OneTimeWorkRequest oneTimeWorkRequest =
new OneTimeWorkRequest.Builder(SyncWorker.class)
.setConstraints(constraints)
.addTag(SyncWorker.TAG)
.build();
According to https://developer.android.com/topic/libraries/architecture/workmanager
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
I was wondering, if SyncWorker keep returning RETRY, what is the retry strategy of WorkManager? For instance, what is the maximum retry count for WorkManager? The documentation isn't clear on this.
The default is BackoffPolicy.EXPONENTIAL.
We only retry when you ask us to RETRY by returning WorkerResult.RETRY or when constraints that were required for your Worker are now unmet. So for e.g. if you required a NETWORK constraint, and now the device lost its active Network connection - then the Worker will be stopped and be automatically retried (when the constraints are met).
For more information look at the docs.
This following example retry 3 times on caught Exception before quit.
class RepeatWorker(context : Context, params : WorkerParameters)
: Worker(context, params) {
private fun doSomeThing() {
// do something
}
override fun doWork(): Result {
if (runAttemptCount > 3) {
return Result.failure()
}
try {
doSomeThing()
}
catch (e: Exception) {
e.printStackTrace()
return Result.retry()
}
return Result.success()
}
}
NOTE: Default BackoffPolicy is exponential, where 1st retry in 30s (minimum retry period is 10s and maximum retry period never exceed 18000s/5 hours).
fun start() : LiveData<WorkInfo> {
val WORK_NAME = "SingleBackupWorker"
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val work = OneTimeWorkRequestBuilder<BackupWorker>()
.setConstraints(constraints)
.setInitialDelay(5, TimeUnit.SECONDS)
.setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 1, TimeUnit.MINUTES)
.build()
WorkManager.getInstance().enqueueUniqueWork(WORK_NAME, ExistingWorkPolicy.REPLACE, work)
return WorkManager.getInstance().getWorkInfoByIdLiveData(work.id)
}
Gets the current run attempt count for any work from runAttemptCount . Note that for periodic work, this value gets reset between periods. Link :-
https://developer.android.com/reference/androidx/work/ListenableWorker#getRunAttemptCount()
example :
override fun doWork(): Result {
if (runAttemptCount < maxRetryConstantIWant) {
.....
.....
.....
} else { Result.Failure }
}
Here runAttemptCount is worker method.