Lädt...


🔧 Use WorkManager Mindfully and Don’t Make These Mistakes


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

WorkManager is a powerful tool, but with great power comes great responsibility. Is it always completely safe to use? In this article, we will discuss a few potentially dangerous situations related to WorkManager. We will focus on the inconsistency of Workers, which can be edited or removed over time.

Image description

Custom WorkerFactory and @AssistedInject

Let's face it, nowadays injecting dependencies into a Worker class is common and nearly inevitable. Our background work often requires sending a request using e.g. Retrofit service, saving something in the database using Dao or simply separating logic from Worker. It would be great if we could inject these dependencies directly into the Worker. Without dependency injection, Workers would not be so powerful. WorkManager creates Workers on its own by default. It expects the Worker to have a constructor with two parameters (Context and WorkerParameters). So how do we provide our dependencies there?

How to inject dependencies using Dagger 2

One of the most common practices is to create a custom WorkerFactory and @AssistedInject. Once we prepare our Assisted factories, we can create workers on our own using WorkerFactory. Here is a sample:

class CustomWorkerFactory @Inject constructor(
    private val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards MyWorkerAssistedFactory>
) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {
        return workerFactories.entries
            .find {
                Class.forName(workerClassName).isAssignableFrom(it.key)
            }
            ?.value
            ?.create(appContext, workerParameters)
    }
}

It assumes that we are able to inject our custom-assisted factories, which will help us create Workers using only Context and WorkerParameters. For providing such factories, please see Assisted Injection documentation.

This code works fine, however there is one issue with it. If you rename, move or delete the Worker’s class and you had a work request scheduled for the class before modification, then the Class.forName(workerClassName) is going to throw ClassNotFoundException. It’s because WorkManager stores class names in its local database and it doesn’t track class modifications. Once WorkManager saves a particular class name, it’s going to stay in the database until the associated request is completed.

Here is a sample scenario showing how this situation can happen:

  1. Create SyncDataWorker class and install the app
  2. Turn off Wi-Fi and cellular data
  3. Schedule a work request with the constraint of having a network available
  4. Modify SyncDataWorker class to SyncWorker and install the app
  5. Turn on Wifi or cellular data and wait for the Worker to start work
  6. If work is not scheduled, please use the following ADB command to debug WorkManager and see if the WorkRequest is enqueued:

adb shell am broadcast -a "androidx.work.diagnostics.REQUEST_DIAGNOSTICS" -p "your.package.name"

  1. The app should crash soon with the ClassNotFoundException

In order to fix this issue we can simply wrap the Class.forName(workerClassName) invocation with a try-catch statement:

class SafeWorkerFactory @Inject constructor(
    private val workerFactories: Map<Class<out ListenableWorker>, @JvmSuppressWildcards MyWorkerAssistedFactory>
) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? = try {
        workerFactories.entries
            .find {
                Class.forName(workerClassName).isAssignableFrom(it.key)
            }
            ?.value
            ?.create(appContext, workerParameters)
    } catch (e: ClassNotFoundException) {
        println("Class not found thrown!!!")
        e.printStackTrace()
        null
    }
}

Now if there was some deprecated class name in the WorkManager’s storage, we won’t encounter a crash due to ClassNotFoundException being thrown. Of course, the request won’t execute, but we’ll talk about it further in this blog.

How to inject dependencies using Hilt

Hilt made it all easier for you. In order to use it for the WorkManager configurations you need to add the following dependency to your project:

implementation("androidx.hilt:hilt-work:<newest_version>")

It provides an already existing safe HiltWorkerFactory ready to be used. This factory also uses Class.forName to get the Worker class by its name, but it’s wrapped with a try-catch statement already. This factory is ready to be injected out of the box once you add Hilt dependency - you don’t need to provide it on your own.

It works together with @HiltWorker annotation which you should add over your Worker class.\
It looks more or less like this:

@HiltAndroidApp
class SafeWorkManagerApp : Application(), Configuration.Provider {

    @Inject
    lateinit var hiltWorkerFactory: HiltWorkerFactory

    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder()
            .setWorkerFactory(hiltWorkerFactory)
            .build()
    }
}

@HiltWorker
class SyncDataWorker(
    context: Context,
    workerParameters: WorkerParameters,
    @Assisted
    someOtherDependencyProvidedByHilt: SomeOtherDependencyProvidedByHilt 
): Worker(context, workerParameters) {
    override fun doWork(): Result {
        // define work and return Result
    }
}

Having this code, you’re ready to go. You can use WorkManager and enqueue work requests with assisted injection.

Any other dangers?

Well, we are covered in terms of catching ClassNotFoundException, but is it completely safe? Well... It depends!

Imagine an OfflinePaymentWorker that is supposed to synchronise offline payments with your backend. Now, you requested a work request for this Worker and it hasn’t completed yet. Then if you e.g. change the name of the Worker from OfflinePaymentWorker to SyncOfflinePaymentsWorker and install the app, you won’t sync outstanding work requests, because our safe factories would return null instead of an actual Worker. You could lose critical data about the payments.

That’s why you have to be always mindful about the Worker changes you introduce. Just keep in mind that WorkManager can store some incomplete work requests in it’s storage and modifying your Worker class might make them impossible to execute.

What to do to prevent losing your data?

Well, there are many approaches you can take. The most obvious one is to keep the old Worker and adjust only the logic - don’t delete it, move it or change the name. The downside of it, is that once you introduce a critical data sync Worker, it probably going to stay with you forever because theoretically, you are never sure if every task in the field has been executed or not.

There are other approaches as well, here is the last one that I am going to present. Instead of relying on WorkManager to store your data in a work request, you could store the critical data in your own storage like SharedPreferences or SQLite database. In other words instead of doing this:

fun enqueueWork() {
    val request = OneTimeWorkRequestBuilder<SyncDataWorker>()
        .setInputData(
            workDataOf(
                "data1" to "value1",
                "data2" to "value2",
            )
        ).build()

    WorkManager.getInstance(this)
        .beginUniqueWork(..., request)
        .enqueue()
}


class SyncDataWorker(
    appContext: Context,
    workerParams: WorkerParameters
) : Worker(appContext, workerParams) {

    override fun doWork(): Result {
        val data1 = inputData.getString("data1") ?: return Result.failure()
        val data2 = inputData.getString("data2") ?: return Result.failure()


        // some logic to synchronise data
        return Result.success()
    }
}

you could store the data1 and data2 values in SQLite Database as a single row representing a work that has to be executed and then create a Worker that would synchronise all of the remaining data from the database:

fun enqueueWork() {
    val syncDataRequest = OneTimeWorkRequestBuilder<SyncDataWorker>()
        .build()

    WorkManager.getInstance(this)
        .beginUniqueWork(..., syncDataRequest)
        .enqueue()
}

@HiltWorker
class SyncDataWorker(
    appContext: Context,
    workerParams: WorkerParameters,
    @Assisted
    syncDataDao: SyncDataDao
) : Worker(appContext, workerParams) {

    override fun doWork(): Result {
        val allDataToSynchronise = syncDataDao.getAll()

        allDataToSynchronize.forEach {
            // some logic to synchronise data
        }

        return Result.success()
    }
}

This way you won’t lose critical data if you modify or remove your Worker class. You would still have it in your database and you would be able to synchronise it in one way or another.

Summary

We have to be mindful of our Workers and make sure that modifying or removing them is not going to cause some issues for our business.

What else do you do to keep WorkManager work safe? Share in the comments!

By Szymon Miloch, Android & Web Developer @ Bright Inventions

...

🔧 Use WorkManager Mindfully and Don’t Make These Mistakes


📈 106.52 Punkte
🔧 Programmierung

🎥 DON'T Make THESE Mistakes When Building A PC (pt. 5)


📈 36.11 Punkte
🎥 Videos

📰 Don't make these cyber resiliency mistakes


📈 36.11 Punkte
📰 IT Security Nachrichten

📰 Cloud computing: Don't make these 10 mistakes


📈 36.11 Punkte
📰 IT Nachrichten

🔧 Don't Make One of These Entity Framework Core Mistakes


📈 36.11 Punkte
🔧 Programmierung

📰 Don't use these passwords: These are the 10 logins most regularly found for sale online


📈 27.93 Punkte
📰 IT Security Nachrichten

🎥 Android Auto apps update, WorkManager 2.5.0, Bazel 4.0, Firebase Experimental Extensions, and more!


📈 26.47 Punkte
🎥 Videos

🎥 Now in Android: 33 - Motion Layout, AndroidX, Hilt, WorkManager, and more!


📈 26.47 Punkte
🎥 Video | Youtube

🎥 Now in Android: 35 - Jetpack Compose Beta, Android 12, WorkManager, and more!


📈 26.47 Punkte
🎥 Video | Youtube

🎥 Now in Android: 36 - MAD Skills WorkManager, AndroidX, #AndroidDevChallenge, and more!


📈 26.47 Punkte
🎥 Video | Youtube

🎥 Now in Android: 37 - Android 12, MAD Skills WorkManager, AndroidX, and more!


📈 26.47 Punkte
🎥 Video | Youtube

📰 Android Jetpack WorkManager Stable Release


📈 24.82 Punkte
🤖 Android Tipps

🎥 WorkManager: Beyond the Basics (Android Dev Summit '19)


📈 24.82 Punkte
🎥 Video | Youtube

📰 Wichtige Sicherheitsupdates für VMware Workmanager & Co. erschienen


📈 24.82 Punkte
📰 IT Security Nachrichten

📰 Wichtige Sicherheitsupdates für VMware Workmanager & Co. erschienen


📈 24.82 Punkte
📰 IT Nachrichten

🎥 Kotlin: Using WorkManager Kotlin APIs - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 Introduction to WorkManager - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Basics - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Working in the background - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Advanced configuration & testing - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Deprecation of GCM NM & FCM JD - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Live Q&A - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

🎥 WorkManager: Community tip - MAD Skills


📈 24.82 Punkte
🎥 Video | Youtube

📰 MAD Skills WorkManager : Wrap-Up


📈 24.82 Punkte
🤖 Android Tipps

🎥 Using WorkManager to upload large files to Cloud Storage


📈 24.82 Punkte
🎥 Video | Youtube

🪟 These finger gloves promise to make you a better gamer, and don't look weird at all


📈 24.07 Punkte
🪟 Windows Tipps

📰 Don’t want your SMSs stolen? Don’t download these Android apps


📈 23.72 Punkte
📰 IT Security Nachrichten

📰 Don't buy these 'Amazon's Choice' video doorbells if you don't want to get hacked


📈 23.72 Punkte
📰 IT Nachrichten

📰 ​These are the biggest WordPress website mistakes and here's how to fix them


📈 23.08 Punkte
📰 IT Security Nachrichten

📰 Victims of these online crooks lacked a key security feature. Don't make the same mistake


📈 22.42 Punkte
📰 IT Nachrichten

🎥 Make sure these events don't overstay their holiday welcome. Go!


📈 22.42 Punkte
🎥 Videos

matomo