HiltWorkerFactory: Configure WorkManagerInitializer at AppStartup
Asked Answered
S

4

13

WorkManagerInitializer requires to configure setWorkerFactory to inject dependencies to the Worker class. The [document][1] explains about workManager Initialization at AppStartup, but does not provide any insights on how to configure setWorkerFactory. If any could suggest any solutions or workaround, that'll be greatly helpful. The problem is that I am unable to inject my own dependencies into the workerClass. I have include two scenarios below to explain the case:` Working Scenario #1:

// This call works fine.

class AppWorker @WorkerInject constructor(
    @Assisted context: Context,
    @Assisted workerParams: WorkerParameters
) : Worker(context, workerParams) {
    companion object {
        val workType = "WorkType"

    }

    override fun doWork(): Result {
        return Result.success()
    }
}

Failed Scenario #2:

// Initializes WorkManager.
class WorkManagerInitializer : Initializer<WorkManager> {
    override fun create(context: Context): WorkManager { 
        // How to get workFactory required for configuration.
        var workerFactory: HiltWorkerFactory? = null
        val configuration = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
        WorkManager.initialize(context, configuration)
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        // No dependencies on other libraries.
        return emptyList()
    }
}



@HiltAndroidApp
class BaseApp: Application(),Configuration.Provider{
    @Inject lateinit var workerFactory: HiltWorkerFactory
    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
    init {
        Thread.setDefaultUncaughtExceptionHandler(ThreadExceptionalHandler())

    }
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            Timber.plant(DebugTree())
        }
    }
}

Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.baseapp">

      <application
        android:name="com.example.baseapp.startup.BaseApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApp"
        >
        <activity android:name="com.example.baseapp.gui.activities.MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="${applicationId}.androidx-startup"
            android:exported="false"
            tools:node="merge">

            <meta-data                android:name="com.example.baseapp.startup.AppServicesInitializer"
                android:value="androidx.startup" />

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

    </application>

</manifest>

// App complies and runs successfully, but fails to call doWork()

  [1]: https://developer.android.com/topic/libraries/app-startup
The AppWorker class dowork() method is not getting called with WorkManagerInitializer defined at AppStartup. Here is the error in logcat:

2020-09-24 19:38:41.811 23803-23863/com.example.baseapp E/WM-WorkerFactory: Could not instantiate com.example.baselib.services.local.work_manager.worker.AppWorker
java.lang.NoSuchMethodException: com.example.baselib.services.local.work_manager.worker.AppWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
    at java.lang.Class.getConstructor0(Class.java:2332)
    at java.lang.Class.getDeclaredConstructor(Class.java:2170)
    at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:95)
    at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:242)
    at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:136)
    at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
    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:923)
Strickle answered 18/9, 2020 at 7:59 Comment(0)
A
9

For people using Hilt with androidx.work-* version 2.6.0-alpha01 or later:

From release notes, this version started using the new androidx.startup jetpack library internally. So, the way to remove WorkManager's default initializer from AndroidManifest.xml has changed a bit.

If you are using androidx.startup elsewhere in your app, replace the old change with:

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- If you are using androidx.startup to initialize other components -->
    <meta-data
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
 </provider>

Otherwise, you can completely disable androidx.startup by replacing the old change with:

<!-- If you want to disable androidx.startup completely. -->
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    tools:node="remove">
</provider>

This way, your HiltWorker will use the factory in your sub-classed App again. Unfortunately, you will lose the (noticeable) benefit of delayed startup.

Antisthenes answered 19/4, 2021 at 16:31 Comment(0)
F
6

We can use HiltWorkerFactory directly from Initializer. Here's the example:

class CustomWorkManagerInitializer : Initializer<WorkManager> {

    override fun create(context: Context): WorkManager {
        val workerFactory = getWorkerFactory(appContext = context.applicationContext)
        val config = Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
        WorkManager.initialize(context, config)
        return WorkManager.getInstance(context)
    }

    override fun dependencies(): MutableList<Class<out Initializer<*>>> = mutableListOf()

    private fun getWorkerFactory(appContext: Context): HiltWorkerFactory {
        val workManagerEntryPoint = EntryPointAccessors.fromApplication(
            appContext,
            WorkManagerInitializerEntryPoint::class.java
        )
        return workManagerEntryPoint.hiltWorkerFactory()
    }

    @InstallIn(SingletonComponent::class)
    @EntryPoint
    interface WorkManagerInitializerEntryPoint {
        fun hiltWorkerFactory(): HiltWorkerFactory
    }
}

Delete HiltWorkerFactory injection in the Application.

@HiltAndroidApp
class BaseApp : Application() {
    override fun onCreate() {
        super.onCreate()
        AppInitializer.getInstance(this).initializeComponent(CustomWorkManagerInitializer::class.java)
    }
}

Also disable default WorkManager initializers and add our custom in manifest.

<provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- disable default -->
        <meta-data
            android:name="androidx.work.WorkManagerInitializer"
            android:value="@string/androidx_startup"
            tools:node="remove" />
        <!-- enable custom -->
        <meta-data
            android:name="com.acme.app.initializer.CustomWorkManagerInitializer"
            android:value="androidx.startup" />
</provider>
Fontenot answered 26/4, 2021 at 21:12 Comment(1)
Hi, for me this works. Thanks!!! I was trying to create a new worker with some dependencies and Worker can't instantiate. This save my day! Thans @FontenotHouseraising
C
5

You can simply inject dependencies by using @WorkerInject annotation like below and get rid of your factory:

class ExampleWorker @WorkerInject constructor(
  @Assisted appContext: Context,
  @Assisted workerParams: WorkerParameters,
  dependency: YourClassDependency
) : Worker(appContext, workerParams) { ... }

Then, have your Application class implement the Configuration.Provider interface, inject an instance of HiltWorkFactory, and pass it into the WorkManager configuration as follows:

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

  @Inject lateinit var workerFactory: HiltWorkerFactory

  override fun getWorkManagerConfiguration() =
      Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()
}

Note that you also must remove the default initializer from the AndroidManifest.xml:

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

Reference: Hilt and Jetpack Integrations

Collaborative answered 18/9, 2020 at 11:0 Comment(4)
I have implemented your suggested solution to inject dependencies to the WorkerClass. But it appears that it is not possible to set "HiltWorkerFactory" in WorkManagerInitializer as described in scenario #2Strickle
@Strickle can you share you manifest.xml as well as your application class?Collaborative
All hilt dependencies excluding 'Work Manager' are initialized at AppStartup . I have commented WorkManagerInitializer class code so that 'HiltWorkerFactory' can be set inside the Application Class.Strickle
Seems like your application is still using WorkManagerInitializer class as described in Scenario 2. Can you share a repo?Collaborative
O
2

To anyone still facing this problem, to remove the default initializer from the AndroidManifest.xml change the following

<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities=\"${applicationId}.androidx-startup"
    android:exported="false"
    tools:node=\"merge">
    <!-- If you are using androidx.startup to initialize other components -->
    <meta-data
        android:name="androidx.work.impl.WorkManagerInitializer"
        android:value="androidx.startup"
        tools:node="remove" />
 </provider>

to

<provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <meta-data
            android:name="androidx.work.WorkManagerInitializer"
            android:value="androidx_startup"
            tools:node="remove" />
    </provider>

The difference is androidx.work.impl.WorkManagerInitializer and androidx.work.WorkManagerInitializer

Olds answered 15/9, 2021 at 16:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.