Open michzio opened 5 years ago
I discussed this exact case in my article. This happens when you have not replaced WorkManager
s default WorkerFactory
. Have you already done this?
I think I have replaced most things correctly.
My code is here:
Worker:
class ProposedDatesEmailWorker
@Inject constructor(
context: Context,
workerParams: WorkerParameters,
var repo: ScheduleRequestRepository
) : Worker(context, workerParams) {
override fun doWork(): Result {
}
}
Application
class MyApplication : Application(), HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun onCreate() {
super.onCreate()
instance = this
// Dagger 2
AppInjector.init(this)
// WorkManager Factory
initWorkManager()
}
override fun activityInjector() = dispatchingAndroidInjector
companion object {
private var instance : Application? = null
val context : Context
get() = instance!!.applicationContext
val sharedInstance : Application
get() = instance!!
}
private fun initWorkManager() {
val appComponent = DaggerAppComponent.builder().application(this).build()
val configuration = Configuration.Builder()
.setWorkerFactory(appComponent.daggerWorkerFactory())
.build()
WorkManager.initialize(this, configuration)
}
}
AppComponent
@Singleton
@Component(
modules = [
AndroidSupportInjectionModule::class,
AppModule::class,
ActivityModule::class]
)
interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
fun inject(app: MyApplication)
// Worker Subcomponent
fun workerFactoryComponentBuilder() : WorkerFactoryComponent.Builder
fun daggerWorkerFactory(): DaggerWorkerFactory
}
WorkerFactoryComponent subcomponent
@Subcomponent(modules = [
WorkerModule::class
])
interface WorkerFactoryComponent {
fun workers(): WorkerMap
@Subcomponent.Builder
interface Builder {
@BindsInstance
fun workerParameters(param: WorkerParameters): Builder
fun build(): WorkerFactoryComponent
}
}
Worker Module
@Suppress("unused")
@Module
internal abstract class WorkerModule {
@Binds
@IntoMap
@WorkerKey(ProposedDatesEmailWorker::class)
abstract fun bindProposedDatesEmailWorker(worker: ProposedDatesEmailWorker) : Worker
}
Dagger Worker Factory
@Singleton
class DaggerWorkerFactory
@Inject constructor(
private val workerFactoryComponent: WorkerFactoryComponent.Builder
) : WorkerFactory() {
private fun createWorker(workerClassName: String, workers: WorkerMap): ListenableWorker? = try {
val workerClass = Class.forName(workerClassName).asSubclass(Worker::class.java)
var provider = workers[workerClass]
if (provider == null) {
for ((key, value) in workers) {
if (workerClass.isAssignableFrom(key)) {
provider = value
break
}
}
}
if (provider == null)
throw IllegalArgumentException("no provider found")
provider.get()
} catch (th: Throwable) {
// log
null
}
override fun createWorker(appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters) =
workerFactoryComponent
.workerParameters(workerParameters)
.build()
.run { createWorker(workerClassName, workers()) }
}
Worker Key
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class WorkerKey(val value: KClass<out Worker>)
Manifest - here I consider wether ${applicationId} shouldn't be given explicitly I have .app, .base other things
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
android:enabled="false"
tools:replace="android:authorities" />
My AppModule with dependencies I would like to inject
@Module(includes = [ViewModelModule::class, NetworkModule::class, RepositoryModule::class, DatabaseModule::class])
class AppModule {
@Provides
fun provideContext(application: Application) : Context {
return application
}
@Provides
@Singleton
fun provideSharedPreferences(application: Application) : SharedPreferences {
return PreferenceManager.getDefaultSharedPreferences(application)
}
@Provides
@Singleton
fun provideSecureStore(application: Application) : SecureStore {
return SecureStore(application)
}
}
Thanks for the snippets, two things.
createWorker
is called?tools:node="remove"
in the content provider removing code. Could you fix it and then run to see if your implementation is being used by WorkManager?I inspected code again from the morning (fresh mind) and have found the issue I have changed this sections of code:
// in MyApplication.onCreate()
// Dagger 2
val appComponent = DaggerAppComponent.builder()
.application(this)
.build()
appComponent.inject(this)
AppInjector.init(this)
// WorkManager Factory
initWorkManager(appComponent)
and then this method
private fun initWorkManager(appComponent : AppComponent) {
val configuration = Configuration.Builder()
.setWorkerFactory(appComponent.daggerWorkerFactory())
.build()
WorkManager.initialize(this, configuration)
}
Previously I have two times invoked this line of code
DaggerAppComponent.builder()
.application(this)
.build()
for
DaggerAppComponent.builder()
.application(this)
.build().inject(this)
and separately for
.setWorkerFactory(appComponent.daggerWorkerFactory())
Thank you guy your blog post was very helpful for me as I have little knowledge of Dagger 2 :)
Oh, I had missed that bit :). Thank you, I am glad you found it helpful!
I have tried this solution but it doesn't inject my Repository object into Worker.
I have such error: E/WorkerFactory: Could not instantiate com.myapp.workers.ProposedDatesEmailWorker java.lang.NoSuchMethodException: [class android.content.Context, class androidx.work.WorkerParameters]
at java.lang.Class.getConstructor0(Class.java:2320)
at java.lang.Class.getDeclaredConstructor(Class.java:2166)
at androidx.work.WorkerFactory.createWorkerWithDefaultFallback(WorkerFactory.java:91)
at androidx.work.impl.WorkerWrapper.runWorker(WorkerWrapper.java:190)
at androidx.work.impl.WorkerWrapper.run(WorkerWrapper.java:124)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)