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)
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>
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With