Óscar
Óscar

Reputation: 1183

Implement an AndroidX Worker with custom parameters

I'm migrating from the Android Priority JobQueue library to the AndroidX WorkManager in my project, but it is built in a way that the Worker should be something like this:

TaskSchedulerWorker.kt

class TaskSchedulerWorker(
    private val workManager: WorkManager = WorkManager.getInstance()
) : TaskScheduler {

    override fun execute(useCaseWrapper: UseCaseWrapper) {
        workManager.beginUniqueWork(
            useCaseWrapper.useCaseName,
            ExistingWorkPolicy.APPEND,
            OneTimeWorkRequest.from(UseCaseWrapperWorker::class.java)
        ).enqueue()
    }

    override fun onCancel(useCaseWrapper: UseCaseWrapper) {
        workManager.cancelUniqueWork(useCaseWrapper.useCaseName)
    }
}

UseCaseWrapperWorker.kt

class UseCaseWrapperWorker(
    context: Context, 
    params: WorkerParams,
    private val useCaseWrapper: UseCaseWrapper
) : Worker(context, params) {

    override fun doWork(): Result {
        return try {
            useCaseWrapper.execute()
            Result.success()
        } catch (ex: Exception) {
            ex.printStackTrace()
            Result.failure()
        }
    }
}

The point is that if I try to run my app with those classes, I receive the next error:

2019-05-09 09:34:45.784 27260-27443/com.mobileapp.debug E/WM-WorkerFactory: Could not instantiate com.mobileapp.domain.jobqueue.UseCaseWrapperWorker
    java.lang.NoSuchMethodException: com.mobileapp.domain.jobqueue.UseCaseWrapperWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
        at java.lang.Class.getConstructor0(Class.java:2328)
        at java.lang.Class.getDeclaredConstructor(Class.java:2167)
        at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:92)
        at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:234)
        at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:128)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)
2019-05-09 09:34:45.784 27260-27443/com.mobileapp.debug E/WM-WorkerWrapper: Could not create Worker com.mobileapp.domain.jobqueue.UseCaseWrapperWorker

Because the Worker constructor can not have more than the two default parameters (Context and WorkerParameters).

I am not able to figure out how to implement the UseCaseWrapperWorker class with a constructor that receives the Context, the WorkerParameters and the UseCaseWrapper, and invoke it from the TaskSchedulerWorker with the OneTimeWorkRequest.from(UseCaseWrapperWorker::class.java) instruction.


EDITED:

I've tried to implement a CustomWorkerFactory and a CustomWorker to pass them the UseCaseWrapper. The problem is that I have to init the WorkManager in the Application#onCreate method (Android Developer Documentation), and I don't have access to the UseCaseWrapper needed at this point.

TaskSchedulerWorker.kt

(the WorkManager init should be in Application#onCreate)

class TaskSchedulerWorker(private val appContext: Context) : TaskScheduler {

    override fun execute(useCaseWrapper: UseCaseWrapper) {
        val workerFactory = CustomWorkerFactory(useCaseWrapper)
        val configuration = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
        WorkManager.initialize(context, configuration)

        WorkManager.getInstance().beginWork(
            "uniqueWorkName",
            ExistingWorkPolicy.APPEND,
            OneTimeWorkRequest.from(CustomWorker::class.java)
        )
    }
}

CustomWorkerFactory.kt

class CustomWorkerFactory(private val useCaseWrapper: UseCaseWrapper) : WorkerFactory() {

    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker? {
        return CustomWorker(appContext, workerParameters, useCaseWrapper)
    }
}

CustomWorker.kt

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

    private var useCaseWrapper: UseCaseWrapper? = null

    constructor(
        context: Context,
        params: WorkerParameters,
        useCaseWrapper: UseCaseWrapper
    ) : this(context, params) {
        this.useCaseWrapper = useCaseWrapper
    }

    override fun doWork(): Result {
        return useCaseWrapper?.let { useCaseWrapper ->
            try {
                useCaseWrapper.execute()
                Result.success()
            } catch (ex: Exception) {
                Result.failure()
            }
        } ?: Result.failure()
    }
}

The RuntimeException thrown is:

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mobileapp.debug/com.mobileapp.home.view.activity.HomeActivity}: java.lang.IllegalStateException: WorkManager is already initialized.  
Did you try to initialize it manually without disabling WorkManagerInitializer? See WorkManager#initialize(Context, Configuration) or the class levelJavadoc for more information.
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3086)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3229)
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:6981)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
Caused by: java.lang.IllegalStateException: WorkManager is already initialized.  Did you try to initialize it manually without disabling WorkManagerInitializer? 
See WorkManager#initialize(Context, Configuration) or the class levelJavadoc for more information.
    at androidx.work.impl.WorkManagerImpl.initialize(WorkManagerImpl.java:137)
    at androidx.work.WorkManager.initialize(WorkManager.java:169)
    at com.mobileapp.domain.worker.TaskSchedulerWorker.execute(TaskSchedulerWorker.kt:20)
    at com.mobileapp.domain.usecase.UseCaseHandler.execute(UseCaseHandler.kt:41)
    at com.mobileapp.domain.usecase.UseCaseCall.execute(UseCaseCall.kt:50)
    at com.mobileapp.home.view.presenter.HomePresenter.getConfigurationJSON(HomePresenter.kt:107)
    at com.mobileapp.home.view.activity.HomeActivity.initComponents(HomeActivity.kt:107)
    at com.mobileapp.home.view.activity.HomeActivity.onCreate(HomeActivity.kt:67)
    at android.app.Activity.performCreate(Activity.java:7326)
    at android.app.Activity.performCreate(Activity.java:7317)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3066)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3229) 
    at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) 
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) 
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loop(Looper.java:214) 
    at android.app.ActivityThread.main(ActivityThread.java:6981) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445) 

Any suggestions?

Upvotes: 2

Views: 3642

Answers (2)

hvar90
hvar90

Reputation: 215

you need to disable the default initializer

If you want to control the initialization process, you must disable the default initializer, then define your own custom configuration.

Disabling the default initializer

To provide your own configuration, you must first remove the default initializer. To do so, update AndroidManifest.xml using the merge rule tools:node="remove":

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

https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration

Upvotes: 1

SumirKodes
SumirKodes

Reputation: 563

You should use a custom WorkerFactory to initialize your Workers. See https://developer.android.com/reference/androidx/work/WorkerFactory

Upvotes: 1

Related Questions