Search code examples
androidandroid-workmanager

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?


Solution

  • 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