1

I have a coroutine worker which I would like to be called periodically at ~ 1 minute intervals. How would you go on doing this?

My worker looks like this:

class CloudDataWorker(context: Context, params: WorkerParameters):
    CoroutineWorker(context, params) {

    companion object {
        const val WORK_NAME = "CloudDataWorker"
    }

    override suspend fun doWork(): Result {
        Timber.d("Background worker started.")

        val repository = Repository(applicationContext);

        return try {
            if(repository.getAllJobs().hasActiveObservers()){
                Timber.d("Found active listeners on cloud data.")
                repository.refreshJobs()
            } else {
                Timber.d("No active listeners for cloud data found.")
            }

            Result.success()

        } catch (e: HttpException) {
            Timber.e("Error during update of cloud data: ${e.message()}")
            Result.retry()
        }
    }
}
7heViking
  • 7,137
  • 11
  • 50
  • 94

2 Answers2

0

Well, the idea is to use One Time request (with initial delay) to setup the work, and use observeForever() to re-setup the work.

Following is an example of showing a Toast message every 5 seconds:

private val backgroundScope = CoroutineScope(Dispatchers.Default)    //standard background thread

private fun initWork() {
    backgroundScope.launch {      //setup work in background thread
        setupToastShowingWork(0)  //no delay at first time

        observeToastShowingWork() //observe work state changes
    }
}

private fun setupToastShowingWork(delayInSeconds: Long) {    //must run in background thread
    val constraints = Constraints.Builder().run {
        setRequiredNetworkType(NetworkType.UNMETERED)    //when using WiFi
        build()
    }

    val oneTimeRequest = OneTimeWorkRequestBuilder<ToastShower>().run { //【for breaking 15 minutes limit we have to use one time request】
        setInitialDelay(delayInSeconds, TimeUnit.SECONDS)    //customizable delay (interval) time
        setConstraints(constraints)
        build()
    }

    WorkManager.getInstance(applicationContext).enqueueUniqueWork(    //【must be unique!!】
        ToastShower::class.java.simpleName,    //work name, use class name for convenient
        ExistingWorkPolicy.KEEP,    //if new work comes in with same name, discard the new one
        oneTimeRequest
    )
}

private suspend fun observeToastShowingWork() {
    withContext(Dispatchers.Main) {    //【must run in Main thread!!】 See below
        WorkManager.getInstance(applicationContext).getWorkInfosForUniqueWorkLiveData(ToastShower::class.java.simpleName).observeForever {
            if (it[0].state == WorkInfo.State.SUCCEEDED) {    //when the work is done
                backgroundScope.launch {    //prevent from running in Main thread
                    setupToastShowingWork(5)    //every 5 seconds
                }
            }
        }
    }
}

Demo: https://youtu.be/7IsQQppKqFs


Helpful reading:

Sam Chen
  • 7,597
  • 2
  • 40
  • 73
0

You can't. Minimum running interval is 15 minutes for PeriodicWork see the docs here.

If you have your app on foreground I suggest you use loop with coroutines and take advantage of Kotlin Flows.

class YourViewModel(val repo: YourRepo) : ViewModel() {

    val yourJobOffers = 
        flow {
            while(true) {
                emit(repo.fetchJobs())
                delay(1000 * 60)
            }
        }
        .launchIn(Dispatchers.IO)
}
Some random IT boy
  • 7,569
  • 2
  • 21
  • 47