Topic: 17 Understanding WorkManger in Android

This is our seventeenth topic from learn android from basic to advance series

Β·

11 min read

Topic: 17 Understanding WorkManger in Android

Hello devs, You already know about services in Android right? So In Android development, both WorkManager and Service are used for background processing, but they serve different purposes and have distinct characteristics.

WorkManager

WorkManager is an Android Jetpack library that simplifies the process of performing background tasks, making it easier to execute deferrable, asynchronous tasks even when the app is not running. This API allows you to schedule jobs (one-off or repeating) and chain and combine jobs. You can also apply execution constraints to them such as triggering when the device is idle or charging, or executing when a content provider changes.

Here's a brief overview of how WorkManager works along with a short example in Kotlin:

  1. Defining a Worker: A Worker is an abstract class where you define the actual work to be done in the background. You override the doWork() method and put your background task logic inside it.
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters

class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        // Do your background work here
        // This is executed on a background thread managed by WorkManager
        return Result.success()
    }
}
  1. Setting Constraints: You can set constraints on when your background task should run using Constraints. For example, you might want your task to run only when the device is charging and connected to Wi-Fi.
import androidx.work.Constraints
import androidx.work.NetworkType

val constraints = Constraints.Builder()
    .setRequiresCharging(true)
    .setRequiredNetworkType(NetworkType.UNMETERED)
    .build()
  1. Creating and Enqueuing Work: You create an instance of OneTimeWorkRequest or PeriodicWorkRequest by passing your Worker class and constraints. Then, you enqueue the work using WorkManager.
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager

val myWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
    .setConstraints(constraints)
    .build()

WorkManager.getInstance(context).enqueue(myWorkRequest)
  1. Observing Work Status (Optional): If you need to observe the status of your background task, you can use WorkManager to get LiveData of WorkInfo and observe it for changes.
WorkManager.getInstance(context).getWorkInfoByIdLiveData(myWorkRequest.id)
    .observe(owner, { workInfo ->
        // Handle work status changes here
    })

That's a basic example of using WorkManager in an Android app. WorkManager takes care of managing tasks efficiently, handling scenarios such as retries, respecting system constraints, and providing a simple API for scheduling and monitoring background work. This helps you focus on implementing your app's features while ensuring that background tasks are executed reliably and efficiently.

WorkManager classes, you need to know

1. Worker:

Worker is an abstract class where you define the actual work to be done in the background. You override the doWork() method and put your background task logic inside it.

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

class MyWorker(context: Context, params: WorkerParameters) : Worker(context, params) {

    override fun doWork(): Result {
        // Do your background work here
        // This is executed on a background thread managed by WorkManager
        return Result.success()
    }
}

2. WorkRequest:

WorkRequest is an abstract class representing a request to do some work, either once (OneTimeWorkRequest) or periodically (PeriodicWorkRequest). You use a WorkRequest to specify which Worker should be used and any additional constraints.

Example with OneTimeWorkRequest:

import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager

val myWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
    .build()

WorkManager.getInstance(context).enqueue(myWorkRequest)

Example with PeriodicWorkRequest:

import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

val periodicWorkRequest = PeriodicWorkRequest.Builder(MyWorker::class.java, 1, TimeUnit.HOURS)
    .build()

WorkManager.getInstance(context).enqueue(periodicWorkRequest)

3. WorkManager:

WorkManager is the central class for managing and executing tasks. You use it to enqueue WorkRequests and observe the status of the work.

Example:

val workManager = WorkManager.getInstance(context)

// Enqueue a OneTimeWorkRequest
workManager.enqueue(myWorkRequest)

// Enqueue a PeriodicWorkRequest
workManager.enqueue(periodicWorkRequest)

// Observe the status of work
workManager.getWorkInfoByIdLiveData(myWorkRequest.id)
    .observe(owner, { workInfo ->
        // Handle work status changes here
    })

4. Constraints:

Constraints allow you to specify conditions under which your work should run, such as network connectivity, battery status, or device charging state.

import androidx.work.Constraints
import androidx.work.NetworkType

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

Example with Constraints:

val myWorkRequest = OneTimeWorkRequest.Builder(MyWorker::class.java)
    .setConstraints(constraints)
    .build()

In summary, WorkManager provides a set of classes and APIs to simplify the scheduling and execution of background tasks in Android apps. By using classes like Worker, WorkRequest, WorkManager, and Constraints, you can efficiently manage background work, ensuring that it executes reliably and efficiently while respecting system constraints and user preferences.

When to use WorkManager

The WorkManager library is a good choice for tasks that are useful to complete, even if the user navigates away from the particular screen or your app.

Some examples of tasks that are a good use of WorkManager:

  • Uploading logs

  • Applying filters to images and saving the image

  • Periodically syncing local data with the network

WorkManager offers guaranteed execution, and not all tasks require that. As such, it is not a catch-all for running every task off of the main thread.

Alright devs, Let's create a simple WorkManager Example. we'll create a simple task that generates a notification every 24 hours.

1. Create a Worker Class:

First, create a Worker class that extends the Worker class and defines the background task to be performed.

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import java.util.*

class NotificationWorker(context: Context, workerParams: WorkerParameters) :
    Worker(context, workerParams) {

    override fun doWork(): Result {
        // Create a notification
        createNotification()

        // Return success
        return Result.success()
    }

    private fun createNotification() {
        val notificationManager =
            applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        val channelId = "work_manager_channel"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "WorkManager Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(applicationContext, channelId)
            .setContentTitle("WorkManager Notification")
            .setContentText("This is a notification from WorkManager!")
            .setSmallIcon(android.R.drawable.ic_dialog_info)
            .build()

        notificationManager.notify(Random().nextInt(), notification)
    }
}

2. Enqueue WorkRequest:

Now, create and enqueue a PeriodicWorkRequest to schedule the execution of the NotificationWorker every 24 hours.

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import java.util.concurrent.TimeUnit

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Create a PeriodicWorkRequest to run NotificationWorker every 24 hours
        val periodicWorkRequest =
            PeriodicWorkRequest.Builder(
                NotificationWorker::class.java,
                1, // repeat interval
                TimeUnit.DAYS
            ).build()

        // Enqueue the work request
        WorkManager.getInstance(applicationContext).enqueue(periodicWorkRequest)
    }
}

3. Add Required Permissions:

Don't forget to add the required permissions in your AndroidManifest.xml file:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

4. Run the App:

Run the application, and you should start receiving notifications from the NotificationWorker every 24 hours.

This example demonstrates how to use WorkManager to schedule and execute background tasks in an Android Kotlin application. WorkManager simplifies the process of handling background tasks, ensuring they run efficiently and reliably even when the app is not in the foreground.

So devs during the start of the blog I mentioned that WorkManager and Service are both mechanisms provided by the Android framework for performing background tasks, but they serve different purposes and have different use cases. Here's a breakdown of the main differences between WorkManager and Service in Android Kotlin:

  1. Lifecycle Awareness:

    • WorkManager: WorkManager is lifecycle-aware, meaning it can schedule tasks to run even when the app's process is killed or the device restarts. It manages the execution of tasks efficiently based on the device's state and constraints.

    • Service: Services are not lifecycle-aware by default. While you can bind a service to a component like an activity, they are generally not designed to handle tasks that need to survive across configuration changes, process death, or system restarts.

  2. Execution Flexibility:

    • WorkManager: WorkManager provides flexibility in executing tasks based on various conditions such as device charging status, network connectivity, and system constraints. It allows you to schedule tasks that are guaranteed to execute, even if the app is not currently active.

    • Service: Services are typically used for tasks that need to run continuously in the background, but they don't offer built-in support for scheduling or managing tasks under different conditions. You have more control over the execution logic within a service, but you need to handle scenarios like system resource constraints and battery optimization manually.

  3. Battery Optimization:

    • WorkManager: WorkManager takes care of optimizing tasks for battery consumption by respecting system battery optimization features. It ensures that tasks are executed efficiently without draining the device's battery excessively.

    • Service: Services are not inherently optimized for battery usage. While you can implement strategies to minimize battery consumption within a service, you need to be mindful of resource usage and implement best practices to avoid draining the battery unnecessarily.

  4. Ease of Use:

    • WorkManager: WorkManager provides a high-level API for scheduling and managing background tasks, making it easier to implement deferrable, asynchronous tasks without worrying about managing threads, process lifecycle, or system constraints.

    • Service: Services offer more flexibility and control over background tasks but require more manual handling of threading, lifecycle management, and system resource constraints. They are suitable for tasks that need fine-grained control or long-running operations within the app's context.

Use a Service when you need continuous background processing, close integration with the UI, or if you are targeting specific Android versions and require fine-grained control over background execution. Use WorkManager when you want to schedule deferrable and periodic tasks while ensuring optimal battery usage and broader compatibility with various Android devices. Okay, devs I will Provide you one table comparing the use cases for Services and Work Manage.

Use CaseServicesWork Manager
Long-running tasksSuitable for tasks that need to run continuously in the background, such as playing music or tracking location updates.Suitable for tasks that are expected to finish within a reasonable time frame, but may need to run in the background, such as uploading/downloading files or syncing data.
Lifecycle awarenessNot inherently lifecycle-aware. Developers need to manage lifecycle callbacks manually to ensure proper behavior.Lifecycle-aware. Automatically respects the lifecycle of the application and its components, optimizing resource usage accordingly.
ThreadingRuns on the main thread by default. Developers need to manage threading manually to avoid blocking the main thread.Executes tasks on a background thread by default, ensuring that long-running tasks do not block the main thread and degrade the user experience.
CompatibilityAvailable in all versions of Android.Introduced in Android Jetpack and available in Android API level 14 (Android 4.0) and higher. Provides backward compatibility and smart scheduling based on the device's API level.
Background constraintsNo built-in support for setting constraints such as network availability or battery optimization. Developers need to implement custom logic to handle such constraints.Provides built-in support for setting constraints such as network availability, battery optimization, and device charging status. Tasks are scheduled and executed based on these constraints, optimizing battery life and network usage.
ObservabilityLimited support for observing the status of tasks. Developers need to implement custom solutions for tracking the progress of background tasks.Provides robust APIs for observing the status and progress of tasks, making it easier to track, monitor, and handle task results.
Foreground tasksIdeal for tasks that require ongoing user interaction or notification, such as playing music in the foreground, handling incoming calls, or performing real-time data processing.Not suitable for foreground tasks. Designed for background tasks that should not interfere with the user experience but need to be executed reliably and efficiently.
Periodic tasksCan be used for executing periodic tasks, but developers need to implement custom logic for scheduling and managing the tasks.Provides built-in support for scheduling periodic tasks with flexible intervals and constraints. Tasks are automatically rescheduled based on the specified constraints, optimizing resource usage.
Network operationsSuitable for network operations such as downloading files or making network requests. Developers need to handle network connectivity changes and retries manually.Provides built-in support for setting network constraints, ensuring that tasks requiring network access are executed only when the network is available, and optimizing battery life and network usage.
Immediate vs. deferred tasksTypically used for tasks that need to be executed immediately or at specific times, such as handling user requests or responding to system events.Suitable for tasks that can be deferred and executed later, such as batch processing, syncing data, or performing resource-intensive operations in the background.
Dependency on system stateRequires developers to manage dependencies on system states (e.g., battery level, network availability) manually, which may lead to suboptimal resource usage and performance.Built-in support for setting constraints based on system states (e.g., battery optimization, network availability), ensuring that tasks are executed efficiently based on the device's current state.
Handling parallel executionSupports parallel execution of multiple tasks using threads or ExecutorService, but developers need to manage concurrency and synchronization manually.Provides a high-level API for defining and scheduling tasks, abstracting away the complexity of managing concurrency and parallel execution, ensuring that tasks are executed efficiently and reliably.

Alright devs, It's time to wrap up this blog. I hope this blog helps you to understand WorkManager. Ok, So our next topic is Jetpack Compose. It's a cool modern toolkit to make your app's design awesome.


Connect with Me:

Hey there! If you enjoyed reading this blog and found it informative, why not connect with me on LinkedIn? 😊 You can also follow my Instagram page for more mobile development-related content. πŸ“²πŸ‘¨β€πŸ’» Let’s stay connected, share knowledge and have some fun in the exciting world of app development! 🌟

Check out my Instagram page

Check out my LinkedIn

Β