2

Scheduling Tasks With Android WorkManager

 3 years ago
source link: https://www.raywenderlich.com/20689637-scheduling-tasks-with-android-workmanager
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Home Android & Kotlin Tutorials

Scheduling Tasks With Android WorkManager

In this WorkManager tutorial, you’ll learn how to schedule different kinds of tasks, test the tasks, as well as debug different tasks.

By Harun Wangereka Apr 7 2021 · Article (25 mins) · Intermediate

5/5 3 Ratings

Version

WorkManager is a useful and important component of Android Jetpack. It allows the app to do things in the background even when the app is exited or the device is restarted.

WorkManager also has many advantages over its predecessors. For instance, it’s battery conscious, allows you to determine the conditions for your task to run such as having a Wi-Fi connection, flexible retrying, and integration with Coroutines and RxJava.

In this tutorial, you’ll build WorkManagerApp. The app downloads an image from a URL and saves the image to the device in the background. During the process, you’ll learn about:

  • WorkManager basics
  • Creating different workers and querying work progress
  • WorkManager initialization types
  • Testing your workers

Getting Started

Download the materials using the Download Materials button at the top or bottom of this tutorial. Open Android Studio 4.1.2 or later and import the starter project.

Build and run the project. You’ll see the following screen:

Looking Into WorkManager

WorkManager is a Jetpack Library that allows you to run deferrable jobs.

To understand how WorkManager operates under the hood, you need to know how it interacts with the Android operating system regarding:

  • Persisting requests
  • Running your requests

Persisting Requests

Adapted from Android Dev Summit ’19 WorkManager Presentation

Once you enqueue your work request, WorkManager stores it in the database, which acts as a Single Source of Truth.

After this, WorkManager sends the work request to JobScheduler for API 23 and above. For API below 23, WorkManager performs a further check. If the device has Play Services installed, WorkManager sends the work request to GCM Network Manager. If it’s not installed, WorkManager uses Alarm Manager to run the work.

WorkManager does all this for you. :] Next, you’ll look at how WorkManager actually runs your work request.

Running Your Request

Adapted from Android Dev Summit ’19 WorkManager Presentation

JobScheduler, GCM Network Manager and Alarm Manager are aware of your work. They’ll start your application if need be, given that the device meets all the constraints. In turn, they’ll request WorkManager to run your work.

There’s also GreedyScheduler, which runs inside the app process. Hence, it doesn’t start your app if it’s in the background. GreedyScheduler isn’t affected by the OS.

Now that you understand how WorkManager operates, it’s time to define your first WorkRequest.

Defining Your Work

You define your work by extending from Worker and overriding doWork. doWork is an asynchronous method that executes in the background.

Note: To learn more about WorkManager basics, checkout the WorkManager Tutorial for Android: Getting Started tutorial.

WorkManager has support for long-running tasks. It keeps the process in these tasks alive when the work is running. Instead of a normal Worker, you’ll use a ListenableWorker if you’re in Java or a CoroutineWorker if you’re using Kotlin Coroutines.

First, navigate to ImageDownloadWorker.kt inside the workers package. You’ll see a class like the one below:

class ImageDownloadWorker(
  private val context: Context,
  private val workerParameters: WorkerParameters
) : CoroutineWorker(context, workerParameters) {
  override suspend fun doWork(): Result {
    TODO("Not yet implemented")
  }
}

The code above does the following:

  • The ImageDownloadWorker class extends CoroutineWorker.
  • ImageDownloadWorker requires instances of Context and WorkerParameters as parameters in the constructor. You pass them to CoroutineWorker, too.
  • It overrides doWork(), a suspend method. It can do long-running tasks off the main thread.

doWork() is pretty bare. You’ll add the code to do work in this method shortly.

Navigate to ImageUtils.kt and add the following extension function:

fun Context.getUriFromUrl(): Uri? {
  // 1
  val imageUrl =
    URL(
      "https://images.pexels.com/photos/169573/pexels-photo-169573.jpeg" +
          "?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
    )
  // 2
  val bitmap = imageUrl.toBitmap()
  
  // 3
  var savedUri: Uri? = null
  bitmap?.apply {
    savedUri = saveToInternalStorage(this@getUriFromUrl)
  }
  return savedUri
}

Here’s a breakdown of what the code above does:

  1. imageUrl converts a link to a qualified URL.
  2. Next, you convert imageUrl to a bitmap using toBitmap().
  3. Last, you save the image bitmap to internal storage. Moreover, you get the URI of the path to the image’s storage location.

Head to ImageDownloadWorker.kt. Replace TODO(“Not yet implemented”) with the following code:

// 1
delay(10000)
// 2
val savedUri = context.getUriFromUrl()
// 3
return Result.success(workDataOf("IMAGE_URI" to savedUri.toString()))

Here’s a code breakdown:

  1. The image downloads quickly. To simulate a near real-world situation, you add a delay of 10,000 milliseconds so the work can take time.
  2. You get the URI from the extension function you added in the previous step.
  3. Last, once you have the URI, you return a successful response to notify that your work has finished without failure. workDataOf() converts a list of pairs to a Data object. A Data object is a set of key/value pairs used as inputs/outputs for ListenableWorker‘s. IMAGE_URI is a key for identifying the result. You’re going to use it to get the value from this worker.

Now, you might see some warnings due to missing imports. At the top of the file, add:

import androidx.work.workDataOf
import com.raywenderlich.android.workmanager.utils.getUriFromUrl
import kotlinx.coroutines.delay

You have defined your work. Next, you’ll create a WorkRequest to run the work.

Creating Your WorkRequest

For your scheduled work to run, the WorkManager service has to schedule it. A WorkRequest contains information of how and when your work will run.

You can schedule your work to run either periodically or once.

For running work periodically, you’ll use a PeriodicWorkRequestBuilder. For one-time work, you’ll use OneTimeWorkRequestBuilder.

Creating a One-Time WorkRequest

To create a one-time request, you’ll begin by adding WorkManager initialization. Navigate to HomeActivity.kt. Add this at the top class definition:

private val workManager by lazy {
  WorkManager.getInstance(applicationContext)
}

Here, you create an instance of WorkManager as a top-class variable. Members of HomeActivity can use this WorkManager instance. Be sure to add the WorkManager import when prompted to do so.

Second, add the following method below onCreate:

private fun createOneTimeWorkRequest() {
  // 1
  val imageWorker = OneTimeWorkRequestBuilder<ImageDownloadWorker>()
    .setConstraints(constraints)
    .addTag("imageWork")
    .build()
  // 2
  workManager.enqueueUniqueWork(
    "oneTimeImageDownload",
    ExistingWorkPolicy.KEEP,
    imageWorker
  )
}

In the code above, you:

  1. Create your WorkRequest. You also set constraints to the work. Additionally, you add a tag to make your work unique. You can use this tag to cancel the work and observe its progress, too.
  2. Finally, you submit your work to WorkManager by calling enqueueUniqueWork.

You might see some warnings due to missing imports. At the top of the file, add:

import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import com.raywenderlich.android.workmanager.workers.ImageDownloadWorker

Still, you’ll see the constraints is still highlighted in red. To resolve this, add the following code below your WorkManager initialization at the top of the class:

private val constraints = Constraints.Builder()
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .setRequiresStorageNotLow(true)
  .setRequiresBatteryNotLow(true)
  .build()

The following constraints are set for work to run. The device must have:

  • An active network connection
  • Enough storage
  • Enough battery

Inside onCreate, add the following code below requestStoragePermissions():

activityHomeBinding.btnImageDownload.setOnClickListener {
  showLottieAnimation()
  activityHomeBinding.downloadLayout.visibility = View.GONE
  createOneTimeWorkRequest()
}

In the code above, you call createOneTimeWorkRequest() when you tap START IMAGE DOWNLOAD. You’re also showing the animation and hiding the layout as you wait for your work to complete.

Build and run the app. The UI remains the same.

Tap START IMAGE DOWNLOAD. Notice that the animation doesn’t stop. Don’t worry, you’ll fix this behavior in the next steps.

Observing Work Progress

With WorkManager, you can observe the progress of your work when the app is in the foreground.

To observe your work’s progress, add the following method below createOneTimeWorkRequest():

private fun observeWork(id: UUID) {
  // 1
  workManager.getWorkInfoByIdLiveData(id)
    .observe(this, { info ->
      // 2
      if (info != null && info.state.isFinished) {
        hideLottieAnimation()
        activityHomeBinding.downloadLayout.visibility = View.VISIBLE
        // 3
        val uriResult = info.outputData.getString("IMAGE_URI")
        if (uriResult != null) {
          showDownloadedImage(uriResult.toUri())
        }
      }
    })
}

The code above:

  1. Gets the work information using the work ID and returns it as observable LiveData.
  2. Adds a null check for the work information and the work completion.
  3. Gets the data from your work and displays the image. It uses the IMAGE_URI key from the worker to get the specific value. Without this key, it can’t get the data from your work.

Add a call to observeWork() at the bottom of createOneTimeWorkRequest():

observeWork(imageWorker.id)

You pass the work ID to observeWork(). You use this ID to observe information about this particular work.

Build and run the app.

Tap START IMAGE DOWNLOAD to start the download. Once the download completes, you’ll see the following screen:

At times, you’ll need to create work that runs periodically, rather than once. In the next section, you’ll create such periodic work requests.

Creating a Periodic WorkRequest

Occasionally, your work needs to run several times, such as daily backups of a messaging app. In such cases, you use a PeriodicWorkRequest to create your WorkRequest.

Add the following code below onCreate:

private fun createPeriodicWorkRequest() {
  // 1
  val imageWorker = PeriodicWorkRequestBuilder<ImageDownloadWorker>(
    15, TimeUnit.MINUTES)
    .setConstraints(constraints)
    .addTag("imageWork")
    .build()
  // 2
  workManager.enqueueUniquePeriodicWork(
    "periodicImageDownload",
    ExistingPeriodicWorkPolicy.KEEP,
    imageWorker
  )
  observeWork(imageWorker.id)
}

In the code above, you:

  1. Use PeriodicWorkRequestBuilder to define your work. Notice that it takes time as a parameter. A restriction requires the interval between successive executions of your work to be at least 15 minutes.
  2. Submit the work to WorkManager by calling enqueueUniquePeriodicWork. You need to pass the uniqueWorkName, existingPeriodicWorkPolicy and the imageWorker itself.

Inside onCreate, replace createOneTimeWorkRequest() with createPeriodicWorkRequest(). The code should now be as follows:

 activityHomeBinding.btnImageDownload.setOnClickListener {
   showLottieAnimation()
   activityHomeBinding.downloadLayout.visibility = View.GONE
   createPeriodicWorkRequest()
 }

Build and run the app. Tap START IMAGE DOWNLOAD to start the download. You’ll notice that the animation doesn’t disappear. This is because periodic work never ends and only has CANCELLED state. After each run, the work will be re-run regardless of the previous state.

Creating a Delayed WorkRequest

A delayed WorkRequest is a OneTime WorkRequest whose execution is delayed by a given duration.

First, add the following code below onCreate:

private fun createDelayedWorkRequest() {
  val imageWorker = OneTimeWorkRequestBuilder<ImageDownloadWorker>()
    .setConstraints(constraints)
    .setInitialDelay(30, TimeUnit.SECONDS)
    .addTag("imageWork")
    .build()
  workManager.enqueueUniqueWork(
    "delayedImageDownload",
    ExistingWorkPolicy.KEEP,
    imageWorker
  )
  observeWork(imageWorker.id)
}

Notice the addition of setInitialDelay(30, TimeUnit.SECONDS), which takes the amount of time you want to delay your work.

Inside onCreate, replace createPeriodicWorkRequest() with createDelayedWorkRequest(). Your final result will be:

 activityHomeBinding.btnImageDownload.setOnClickListener {
   showLottieAnimation()
   activityHomeBinding.downloadLayout.visibility = View.GONE
   createDelayedWorkRequest()
 }

Build and run the app. Tap START IMAGE DOWNLOAD to start the download, which delays for 30 seconds. After this delay is complete, your work begins to run. When it finishes, you’ll see the following screen:

You’ve seen how to create your work and define and run your WorkRequest. It’s time for you to learn how to query work information.

Querying Work Information

WorkManager allows you to write complex queries. You use the unique work name, tag and state, using WorkQuery objects.

To query information about the work you’ve run, add the following code below observeWork():

private fun queryWorkInfo() {
  // 1
  val workQuery = WorkQuery.Builder
    .fromTags(listOf("imageWork"))
    .addStates(listOf(WorkInfo.State.SUCCEEDED))
    .addUniqueWorkNames(
       listOf("oneTimeImageDownload", "delayedImageDownload", 
         "periodicImageDownload")
    )
    .build()
  // 2
  workManager.getWorkInfosLiveData(workQuery).observe(this) { workInfos->
    activityHomeBinding.tvWorkInfo.visibility = View.VISIBLE
    activityHomeBinding.tvWorkInfo.text =
      resources.getQuantityString(R.plurals.text_work_desc, workInfos.size, 
        workInfos.size)
  }
}

Here’s what the code above does:

  1. This is a WorkQuery builder, and you’re setting the tag, state and unique names for the works you want to get their information.
  2. You call getWorkInfosLiveData() and pass workQuery. This method returns observable data.

To wire everything up, add the following code inside onCreate:

activityHomeBinding.btnQueryWork.setOnClickListener {
  queryWorkInfo()
}

In the code above, you set a click listener to the QUERY WORK INFO button, which calls queryWorkInfo().

Build and run the app. Tap START IMAGE DOWNLOAD and wait for the download to complete.

Tap QUERY WORK INFO:

The WorkQuery returns the completed work and the result is shown on the screen.

In the next section, you’ll learn how to initialize WorkManager in different ways.

WorkManager Initialization Types

WorkManager has two types of initialization:

  • Automatic Initialization: WorkManager initializes automatically with default configuration and no customizations. The initialization happens in your application onCreate. It can add an extra overhead when WorkManager isn’t in use.
  • On-Demand Initialization: This lets you define WorkManager when you need it. It removes the overhead from app startup and in turn improves your app performance. This initialization is extremely helpful when you need more informative WorkManager logs.

Using On-Demand Initialization

To use On-Demand Initialization, you first need to disable automatic initialization. To achieve this, head over to AndroidManifest.xml. Replace TODO ADD PROVIDER with:

<provider
  android:name="androidx.work.impl.WorkManagerInitializer"
  android:authorities="${applicationId}.workmanager-init"
  tools:node="remove" />

Be sure to add the tools import when prompted to do so. This removes the default WorkManager initializer.

Second, you’ll need to change your application class to add a new configuration provider. Head to WorkManagerApp.kt and replace the class with the following code:

// 1
class WorkManagerApp : Application(), Configuration.Provider {
  // 2
  override fun getWorkManagerConfiguration(): Configuration {
    return Configuration.Builder()
      .setMinimumLoggingLevel(android.util.Log.DEBUG)
      .build()
  }

  override fun onCreate() {
    super.onCreate()
    // 3
    WorkManager.initialize(this, workManagerConfiguration)
  }
}

In the code above:

  1. The application class extends Configuration.Provider. This allows you to provide your own custom WorkManager configuration.
  2. You provide your own implementation of getWorkManagerConfiguration(), in which you provide your custom WorkManager initialization. You also specify the minimum logging level.
  3. Last, you initialize WorkManager with a custom configuration you’ve created.

You might see some warnings due to missing imports. At the top of the file, add:

import androidx.work.Configuration
import androidx.work.WorkManager

Build and run the app. The functionality does not change. Now, you’re using on-demand initialization for your WorkManager.

Testing Your Workers

Testing is one of the most important aspects of software development. WorkManager provides comprehensive APIs to enable you to write tests. This is very important because you can test your workers and confirm that everything runs as expected.

Inside the androidTest directory, create a new file named ImageDownloadWorkerTest.kt and add the following code:

class ImageDownloadWorkerTest {

  // 1
  @get:Rule
  var instantTaskExecutorRule = InstantTaskExecutorRule()

  @get:Rule
  var workerManagerTestRule = WorkManagerTestRule()

  // 2
  @Test
  fun testDownloadWork() {
    // Create Work Request
    val work = TestListenableWorkerBuilder<ImageDownloadWorker>(workerManagerTestRule.targetContext).build()
    runBlocking {
      val result = work.doWork()
      // Assert
      assertNotNull(result)
    }
  }
}

To sum up what the above code does:

  1. You get InstantTaskExecutorRule, which swaps the background executor used by the Architecture Components with a different one that executes each task synchronously. You also get WorkManagerTestRule, which initializes WorkManager and also provides a context.
  2. This is the actual test, which is to test your ImageDownloadWorker. You’re using TestListenableWorkerBuilder, which runs your Worker. In this case, your Worker is a CoroutineWorker.

Add the following imports to resolve all errors:

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.work.testing.TestListenableWorkerBuilder
import com.raywenderlich.android.workmanager.workers.ImageDownloadWorker
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertNotNull
import org.junit.Rule
import org.junit.Test

Click the Run icon to the left of testDownloadWork(). You’ll see the following:

Congratulations! :] You wrote your first Worker test. Next, you’ll be writing tests for your WorkRequest.

Note: WorkManager tests need the Android platform to run. This is the reason your tests are in the androidTest directory.

Testing Delayed Work

Open SampleWorker.kt inside workers and add the following code:

import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters

class SampleWorker (
  private val context: Context,
  private val workerParameters: WorkerParameters
) : Worker(context, workerParameters) {
  override fun doWork(): Result {
    return when (inputData.getString("Worker")) {
      "sampleWorker" -> Result.success()
      else -> Result.retry()
    }
  }
}

The class above extends from Worker and overrides doWork(). It checks the input data and returns success when it finds the sampleWorker input. This SampleWorker is for testing purposes. It shows you how to test normal Workers.

Head to SampleWorkerTest.kt, which is under the androidTest directory. This class is bare and has only the rules at the top. You’ll be adding tests in this class.

Below workerManagerTestRule, add the following code:

@Test
fun testWorkerInitialDelay() {
  val inputData = workDataOf("Worker" to "sampleWorker")
  // 1
  val request = OneTimeWorkRequestBuilder<SampleWorker>()
    .setInitialDelay(10, TimeUnit.SECONDS)
    .setInputData(inputData)
    .build()
  
  // 2
  val testDriver = WorkManagerTestInitHelper
    .getTestDriver(workerManagerTestRule.targetContext)
  val workManager = workerManagerTestRule.workManager
  // 3
  workManager.enqueue(request).result.get()
  // 4
  testDriver?.setInitialDelayMet(request.id)
  // 5
  val workInfo = workManager.getWorkInfoById(request.id).get()
  // 6
  assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}

With the code above, you:

  1. Create your WorkRequest using OneTimeWorkRequestBuilder. You set an initial delay of 10 seconds. You’re also setting input data to your request.
  2. Create a TestDriver, which will help in simulating the delay. You’re creating an instance of WorkManager, too.
  3. Enqueue your request.
  4. Simulate the actual delay for your work.
  5. Get work info and output data.
  6. Do an assertion to check the succeeded state in your work.

To resolve import errors, add the following imports at the top of the file:

import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo
import androidx.work.testing.WorkManagerTestInitHelper
import androidx.work.workDataOf
import com.raywenderlich.android.workmanager.workers.SampleWorker
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert.assertThat
import java.util.concurrent.TimeUnit
import org.junit.Test

Click the Run icon to the left of testWorkerInitialDelay(). You’ll see the following:

You’ve learned how to test delayed workers. Next, you’ll test periodic work requests.

Testing Periodic Work

Still on the SampleWorkerTest.kt, below testWorkerInitialDelay, add:

@Test
fun testPeriodicSampleWorker() {
  val inputData = workDataOf("Worker" to "sampleWorker")
  // 1
  val request = PeriodicWorkRequestBuilder<SampleWorker>(15, TimeUnit.MINUTES)
    .setInputData(inputData)
    .build()
  // 2
  val testDriver = WorkManagerTestInitHelper
    .getTestDriver(workerManagerTestRule.targetContext)
  val workManager = workerManagerTestRule.workManager
  // 3
  workManager.enqueue(request).result.get()
  // 4
  testDriver?.setPeriodDelayMet(request.id)
  // 5
  val workInfo = workManager.getWorkInfoById(request.id).get()
  
  // 6
  assertThat(workInfo.state, `is`(WorkInfo.State.ENQUEUED))
}

In this code, you:

  1. Create your WorkRequest using PeriodicWorkRequestBuilder, with a time interval of 10 minutes. You’re also setting input data to your request.
  2. Make a testDriver, which will help in simulating the time interval between each work. You’re creating an instance of workManager, too.
  3. Enqueue your request.
  4. Notify the WorkManager testing framework that the interval’s duration is complete.
  5. Get work info and output data.
  6. Do an assertion to check for the enqueued state for your work.

Click the Run icon to the left of testPeriodicSampleWorker(). You’ll see the following:

You’ve now learned how to test periodic work requests. Next, you’ll test constraints.

Testing Constraints

SampleWorker needs only two constraints: active network connection and enough battery. TestDriver has setAllConstraintsMet for simulating constraints.

In your SampleWorkerTest, below testPeriodicSampleWorker, add:

@Test
fun testAllConstraintsAreMet() {
  val inputData = workDataOf("Worker" to "sampleWorker")
  // 1
  val constraints = Constraints.Builder()
    .setRequiredNetworkType(NetworkType.CONNECTED)
    .setRequiresBatteryNotLow(true)
    .build()
  // 2
  val request = OneTimeWorkRequestBuilder<SampleWorker>()
    .setConstraints(constraints)
    .setInputData(inputData)
    .build()
  val workManager = WorkManager
    .getInstance(workerManagerTestRule.targetContext)
  // 3
  workManager.enqueue(request).result.get()
  // 4
  WorkManagerTestInitHelper.getTestDriver(workerManagerTestRule.targetContext)
    ?.setAllConstraintsMet(request.id)
  // 5
  val workInfo = workManager.getWorkInfoById(request.id).get()
  // 6
  assertThat(workInfo.state, `is`(WorkInfo.State.SUCCEEDED))
}

Here’s a breakdown of the code above. It:

  1. Creates your network and battery constraints.
  2. Makes your WorkRequest, using OneTimeWorkRequestBuilder. It sets your constraints to the request. It also sets input data to your request.
  3. Enqueues your request
  4. Simulates the network and battery constraints, using WorkManagerTestInitHelper
  5. Gets work info and output data
  6. Does an assertion to check for the succeeded state for your work

Click the Run icon to the left of testAllConstraintsAreMet(). You’ll see the following:

All tests have passed! :] That is truly a reason to be proud of yourself! In the next section, you’re going to look at how to create works that run in the foreground.

Creating Foreground Work

For long-running tasks, you need to show a persistent notification. This prevents the Android system from killing your process when your app is no longer running.

For such cases, WorkManager uses a foreground service to show a persistent notification to the user. With CoroutineWorker, you’ll use setForeground() to specify that your WorkRequest is important or long-running.

Navigate to ImageDownloadWorker.kt and add this below doWork():

private fun createForegroundInfo(): ForegroundInfo {
  // 1
  val intent = WorkManager.getInstance(applicationContext)
    .createCancelPendingIntent(id)
  // 2
  val notification = NotificationCompat.Builder(
     applicationContext, "workDownload")
    .setContentTitle("Downloading Your Image")
    .setTicker("Downloading Your Image")
    .setSmallIcon(R.drawable.notification_action_background)
    .setOngoing(true)
    .addAction(android.R.drawable.ic_delete, "Cancel Download", intent)
  // 3
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    createChannel(notification, "workDownload")
  }
  return ForegroundInfo(1, notification.build())
}

Here is what’s happening:

  1. This is a PendingIntent you’ll use to cancel the work.
  2. This is the actual notification with an icon, title and cancel action. The cancel action is necessary for canceling work.
  3. You’re creating a notification channel for Android versions above Oreo. Then, you return ForegroundInfo, which you use to update your ongoing notification.

Resolve all import errors when prompted. You’ll notice createChannel(notification, "workDownload") remains highlighted in red. To resolve this, add this method below createForegroundInfo():

@RequiresApi(Build.VERSION_CODES.O)
private fun createChannel(
  notificationBuilder: NotificationCompat.Builder, 
  id: String
) {
  val notificationManager =
    context.getSystemService(Context.NOTIFICATION_SERVICE) as
        NotificationManager
  notificationBuilder.setDefaults(Notification.DEFAULT_VIBRATE)
  val channel = NotificationChannel(
    id,
    "WorkManagerApp",
    NotificationManager.IMPORTANCE_HIGH
  )
  channel.description = "WorkManagerApp Notifications"
  notificationManager.createNotificationChannel(channel)
}

In the code above, you create a notification channel. The channel is necessary for devices with version Oreo and above. Add all imports as prompted by the IDE.

Now, to get the notification working, add this in doWork() above your call to delay():

setForeground(createForegroundInfo())

Here, you call setForeground() from CoroutineWorker to mark your work as long-running or important.

Build and run the app. Then tap on START IMAGE DOWNLOAD.

Once your download image task starts, you’ll see a persistent notification. This informs the Android operating system that your task is long-running or important. The operating system won’t kill your task once your app is in the background. Once WorkManager completes the task, the notification will disappear.

Requesting Diagnostic Information from WorkManager

WorkManager provides a way to get the following information:

  1. Requests that have been completed in the past 24 hours
  2. Requests that have been scheduled
  3. Running work requests

This information is available for debug builds. To get this information, run this command on your terminal:

adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" -p "com.raywenderlich.android.workmanager"

Your logcat displays the following information:

Where to Go From Here?

Download the final version of this project using the Download Materials button at the top or bottom of this tutorial.

Congratulations! You learned how to use WorkManager to run one-time, delayed, periodic, and long-running or important tasks in the background. You also learned how to write tests for your workers.

To learn more about WorkManager, check out the WorkManager official documentation.

I hope you enjoyed the tutorial. If you have any questions or comments, please join the forum discussion below.

raywenderlich.com Weekly

The raywenderlich.com newsletter is the easiest way to stay up-to-date on everything you need to know as a mobile developer.

Get a weekly digest of our tutorials and courses, and receive a free in-depth email course as a bonus!

Average Rating

5/5

Add a rating for this content

Sign in to add a rating
3 ratings

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK