arunkumar9t2 / dagger-workmanager

Sample app demonstrating how to construct inject WorkManager's Worker instances using Dagger 2
https://www.arunkumarsampath.in/dagger-recipes-illustrative-step-by-step-guide-to-achieve-constructor-injection-in-workmanager/
MIT License
20 stars 2 forks source link

I cannot inject repository object using this dagger solution. #1

Open michzio opened 5 years ago

michzio commented 5 years ago

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)

arunkumar9t2 commented 5 years ago

I discussed this exact case in my article. This happens when you have not replaced WorkManagers default WorkerFactory. Have you already done this?

michzio commented 5 years ago

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)
    }

}
arunkumar9t2 commented 5 years ago

Thanks for the snippets, two things.

michzio commented 5 years ago

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())

michzio commented 5 years ago

Thank you guy your blog post was very helpful for me as I have little knowledge of Dagger 2 :)

arunkumar9t2 commented 5 years ago

Oh, I had missed that bit :). Thank you, I am glad you found it helpful!