Step #7: Scheduling the Work

Of course, it would be nice if we actually taught WorkManager to call doWork() on our ImportWorker. Otherwise, ImportWorker will be unused.

We want to teach WorkManager to check for new to-do items every so often. And that check should be automatic — other than opting into the checks via our new SwitchPreference, the use should not need to do anything else. So, one place we could add in the WorkManager configuration would be ToDoApp, as it could watch for changes in our SwitchPreference and update WorkManager to match.

This means that ToDoApp will not only need to configure Koin but to get objects from Koin. To do that, we use the same KoinComponent approach that we did with ImportWorker. So, add the KoinComponent interface to the ToDoApp declaration:

class ToDoApp : Application(), KoinComponent {

Now, ToDoApp() can use by inject(), the way ImportWorker did.

Then, add this line above the ToDoApp declaration:

private const val TAG_IMPORT_WORK = "doPeriodicImport"

This sets up a constant String that we will reference shortly.

Next, add this function to ToDoApp:

  private fun scheduleWork() {
    val prefs: PrefsRepository by inject()
    val appScope: CoroutineScope by inject(named("appScope"))
    val workManager = WorkManager.getInstance(this)

    appScope.launch {
      prefs.observeImportChanges().collect {
        if (it) {
          val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()
          val request =
            PeriodicWorkRequestBuilder<ImportWorker>(15, TimeUnit.MINUTES)
              .setConstraints(constraints)
              .addTag(TAG_IMPORT_WORK)
              .build()

          workManager.enqueueUniquePeriodicWork(
            TAG_IMPORT_WORK,
            ExistingPeriodicWorkPolicy.REPLACE,
            request
          )
        } else {
          workManager.cancelAllWorkByTag(TAG_IMPORT_WORK)
        }
      }
    }
  }

This will require an extension function for collect():

import kotlinx.coroutines.flow.collect

We use by inject() to obtain our PrefsRepository and our appScope-named CoroutineScope. We then get a WorkManager instance via the getInstance() factory method.

Then, we use appScope to launch() a coroutine that calls collect() on the Flow returned by observeImportChanges() on PrefsRepository. Each time the SwitchPreference changes, our collect() lambda expression will be executed.

In there, we branch based on the Boolean value that we get, scheduling the work if it is true (i.e., the switch was checked) and canceling the work if it is false (i.e., the switch was unchecked).

To schedule the work, we need to establish some constraints, telling WorkManager any requirements of our environment that should be met before bothering to have us do the work. For that, we use Constraints.Builder. Our one constraint is that we need an Internet connection, so we can reach our server. To specify that, we use setRequiredNetworkType(NetworkType.CONNECTED) on the Constraints.Builder to say that we need an active network connection. We then build() the resulting Constraints. There are other possible constraints that we could set (e.g., the device must be idle), but this all that we need.

We then create a PeriodicWorkRequest using a PeriodicWorkRequestBuilder. A PeriodicWorkRequest represents what should be done and when it should be done. In our case, we are saying that:

Then, to actually schedule the work, we call enqueueUniquePeriodicWork() on the WorkManager instance. Here, “enqueue” means that we want to add this PeriodicWorkRequest to the roster of work to be performed, and “unique” means “if there is already some work with our unique name, resolve it using the supplied policy”. In our case, the unique name is that same TAG_IMPORT_WORK value (though it could be something else if we wanted), and the policy is REPLACE (so if we try scheduling a duplicate piece of work, cancel the existing one).

Canceling the work when the switch is toggled off is much simpler: just call cancelAllWorkByTag() supplying the tag used in the PeriodicWorkRequestBuilder.

Finally, modify onCreate() of ToDoApp to call scheduleWork() after configuring Koin:

  override fun onCreate() {
    super.onCreate()

    startKoin {
      androidLogger()
      androidContext(this@ToDoApp)
      modules(koinModule)
    }

    scheduleWork()
  }

The net effect is that when the app starts up, we start watching for changes in the SwitchPreference, and we schedule or cancel work based on those changes. Note that observeImportChanges() does not emit the existing value of the SwitchPreference, only changes while the app is running. That’s fine, because WorkManager periodic requests are durable and live after our process is terminated, so we only need to teach WorkManager about changes in what we want the work to be.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.