Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HiltWorkerFactory: Configure WorkManagerInitializer at AppStartup

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)
like image 224
Chandra Avatar asked Sep 18 '20 07:09

Chandra


3 Answers

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>
like image 36
vivanov Avatar answered Oct 27 '22 11:10

vivanov


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.

like image 104
Ace Avatar answered Oct 27 '22 09:10

Ace


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

like image 28
Nicola Gallazzi Avatar answered Oct 27 '22 09:10

Nicola Gallazzi