Android Background Processing

Sep 23 2022 · Kotlin 1.6, Android 12, Android Studio Chipmunk 2021.2.1

Part 2: Use WorkManager in Complex Apps

11. Challenge - WorkManager

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 10. Chain Work Next episode: 12. Use DownloadManager

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Notes: 11. Challenge - WorkManager

The student materials have been reviewed and are updated as of SEPTEMBER 2022.

Transcript: 11. Challenge - WorkManager

Nice job repeating the concepts from the first part of the course, and reaching your first challenge in this part! :] In this challenge, you have to implement a few things! You need to create not one, but two new workers!

One to clear the storage before synchronizing images, and of course one to synchronize images from the server. You can reuse some code you’ve written in the first part of the course, and the download logic from this part of the course!

To synchronize images, you need to fetch the list of images from the RemoteApi, and then download them one at a time! That’s it, now pause the video, and solve the challenge, then once you’re done, unpause the video, and compare the two solutions! :] Good luck!

Start by creating a new worker class, named ClearLocalStorageWorker, with the following code:

class ClearLocalStorageWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

  override fun doWork(): Result {
    val rootFolder = applicationContext.externalMediaDirs.first()

    rootFolder?.listFiles()?.forEach {
      if (it.isDirectory) {
        it.deleteRecursively()
      } else {
        it.delete()
      }
    }

    return Result.success()
  }
}

The code is simple, as it lists all the files in this app’s directory, and deletes them! Then create another worker, to sychronize images, named SynchronizeImagesWorker:

class SynchronizeImagesWorker(context: Context, workerParameters: WorkerParameters) :
    Worker(context, workerParameters) {

  override fun doWork(): Result {
    val images = inputData.getStringArray("images") ?: return Result.failure()

    images.forEach { imagePath ->
      val file = File(applicationContext.externalMediaDirs.first(), imagePath)
      FileUtils.downloadImage(file, imagePath)
    }

    return Result.success()
  }
}

This worker is simple too, as it receives a list of images, and then downloads each image accordingly. The FileUtils.downloadImage() function doesn’t exist yet, so let’s create it.

Open the DownloadImageWorker, and cut and paste the download image code, to the FileUtils file, like so:

  fun downloadImage(file: File, imageDownloadPath: String) {
    val imageUrl = URL("$BASE_URL/files/$imageDownloadPath")

    val connection = imageUrl.openConnection() as HttpURLConnection
    connection.doInput = true
    connection.connect()

    val inputStream = connection.inputStream

    val outputStream = FileOutputStream(file)
    outputStream.use { output ->
      val buffer = ByteArray(4 * 1024)

      var byteCount = inputStream.read(buffer)
      while (byteCount > 0) {
        output.write(buffer, 0, byteCount)

        byteCount = inputStream.read(buffer)
      }

      output.flush()
    }
  }

Then head back over to the DownloadImageWorker again, and replace the download code with a function call to the FileUtils.

val imagePath = parts.last()
val file = File(applicationContext.externalMediaDirs.first(), imagePath)

FileUtils.downloadImage(file, imagePath)

Now that you’ve finished the worker code, you can implement the synchronization, in the SettingsFragment. Open the SettingsFragment, and add the following code to the start of the file:

  private val remoteApi = App.remoteApi
  private val networkStatusChecker by lazy {
    NetworkStatusChecker(activity?.getSystemService(ConnectivityManager::class.java))
  }

This will let you use the RemoteApi to fetch the images. Now in the initUi() function, add the code to handle clicks on the synchronize button:

    syncImages.setOnClickListener {
      networkStatusChecker.performIfConnectedToInternet {
        GlobalScope.launch(Dispatchers.Main) {
          val result = remoteApi.getImages()

          if (result is Success) {
            val images = result.data.map { it.imagePath }
            synchronizeImages(images)
          }
        }
      }
    }
  }

Every time you tap the synchronize button, you’ll check if there is an Internet connection available, and then launch a coroutine which will fetch image paths, and begin the synchronization.

Now implement the synchronizeImages function, like so:

  private fun synchronizeImages(images: List<String>) {
    val clearStorageWorker = OneTimeWorkRequestBuilder<ClearLocalStorageWorker>()
        .build()

    val synchronizeImagesWorker = OneTimeWorkRequestBuilder<SynchronizeImagesWorker>()
        .setInputData(workDataOf("images" to images.toTypedArray()))
        .build()

    val workManager = WorkManager.getInstance(requireActivity())

    workManager.beginWith(clearStorageWorker)
        .then(synchronizeImagesWorker)
        .enqueue()

    workManager.getWorkInfoByIdLiveData(synchronizeImagesWorker.id).observe(this, Observer {
      if (it.state.isFinished) {
        activity?.toast("Synchronized images!")
      }
    })
  }

Like before, you’re queueing two workers, first to clear the storage, and then to download all the images. Then you’re listening to the synchronization result, and once it finishes, you toast a message to the user! :] Nice job! Now run the app, and check that everything works!