Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Inject Coroutine Worker With Hilt #2601

Closed
wakaztahir opened this issue May 9, 2021 · 35 comments
Closed

Cannot Inject Coroutine Worker With Hilt #2601

wakaztahir opened this issue May 9, 2021 · 35 comments

Comments

@wakaztahir
Copy link

wakaztahir commented May 9, 2021

I am using Hilt With Coroutine Worker , I am getting this error when work is being dispatched !
I have searched for this issue but couldn't find a solution , any help would be appreciated !

Error

E/WM-WorkerFactory: Could not instantiate com.wakaztahir.timeline.utils.workers.NotificationSetupWorker
    java.lang.NoSuchMethodException: com.wakaztahir.timeline.utils.workers.NotificationSetupWorker.<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:244)
        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)
E/WM-WorkerWrapper: Could not create Worker com.wakaztahir.timeline.utils.workers.NotificationSetupWorker

Application Class

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

    @Inject
    lateinit var workerFactory: HiltWorkerFactory
    
    override fun getWorkManagerConfiguration() =
        Configuration.Builder()
            .setWorkerFactory(workerFactory)
            .build()

}

Here's the actual Worker constructor

@HiltWorker
class NotificationSetupWorker @AssistedInject constructor(
    @Assisted val context: Context,
    @Assisted workerParams: WorkerParameters,
    val reminderDao: ReminderDao
) : CoroutineWorker(context, workerParams)

Gradle

Project Level :

classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" // hilt_version = 2.35.1 & kotlin_version = 1.4.32

App Level :

plugins {
    id 'kotlin-kapt'
    //Hilt Plugin
    id 'dagger.hilt.android.plugin'
}
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-work:1.0.0'
kapt 'androidx.hilt:hilt-compiler:1.0.0'

//Work Manager
def work_version = "2.6.0-alpha02"
implementation "androidx.work:work-runtime-ktx:$work_version"
implementation 'com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava'

https://stackoverflow.com/questions/67388068/injecting-coroutineworker-with-hilt-could-not-instantiate-woker

@danysantiago
Copy link
Member

Hmm, this all looks fine...

Double check your import statements and that you have your TimelineApp class registered in the AndroidManifest.xml

If possible if you attach a debugger in HiltWorkerFactory are you able to see a Provider for your worker?

@wakaztahir
Copy link
Author

wakaztahir commented May 11, 2021

Application class is registered in AndroidManifest.xml

I set a break point on return new HiltWorkerFactory in provideFactory method and it got called , with my workers listed in a map

Capture

But this method was not called

Capture

And this is happening

Capture

I am not instantiating app class so i think its registering

@wakaztahir
Copy link
Author

wakaztahir commented May 11, 2021

I've fixed the issue

Since using work manager version above than 2.6.0-alpha01 , Work Manager version above 2.6.0-alpha01 uses startup initializer Read Here

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

https://stackoverflow.com/questions/67388068/injecting-coroutineworker-with-hilt-could-not-instantiate-woker did the trick !

@forntoh
Copy link

forntoh commented Aug 25, 2021

It stopped working in my Compose app

@wakaztahir
Copy link
Author

Make sure your Gradle version and kotlin version match and updated , proceed with by providing this provider in manifest and see if it fixes

@wakaztahir
Copy link
Author

I use compose and it works well

@forntoh
Copy link

forntoh commented Aug 25, 2021

@wakaztahir I'm using Android Gradle Plugin 7.1.0-alpha09 and Kotlin 1.5.21

  • I have even downgraded to WorkManager version 2.5.0
  • Added this to manifest
    <provider
          android:name="androidx.work.impl.WorkManagerInitializer"
          android:authorities="${applicationId}.workmanager-init"
          tools:node="remove" />
  • App extends Configuration.Provider
  • App is registered in the Manifest
  • Override config
    override fun getWorkManagerConfiguration() =
          Configuration.Builder()
              .setWorkerFactory(workerFactory)
              .build()
  • This is how my worker looks like
    @HiltWorker
    class FirebaseTokenScheduler @AssistedInject constructor(
      @Assisted context: Context,
      @Assisted workerParams: WorkerParameters,
      private val firebaseTokenRepo: FirebaseTokenRepo,
    ) : CoroutineWorker(context, workerParams), CoroutineScope {
    ```

Despite all that I still get Could not instantiate be.yakka.app.scheduler.FirebaseTokenScheduler

@wakaztahir
Copy link
Author

wakaztahir commented Aug 26, 2021

This provider is only for WorkManager version above than 2.6.0-alpha01 so I would suggest you to update WorkManger and see if it works

Wrong :

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

Correct :

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

@wakaztahir
Copy link
Author

wakaztahir commented Aug 26, 2021

try 2.6.0-rc01 or 2.6.0-beta02 or 2.7.0-alpha05

@abhriyaroy
Copy link

Hi @forntoh did you find anything to make it work?

@wakaztahir
Copy link
Author

wakaztahir commented Sep 7, 2021

@abhriyaroy What error are you getting & which version of Work Manager are you using ?

@abhriyaroy
Copy link

abhriyaroy commented Sep 7, 2021

Hi @wakaztahir, I am using workmanager 2.6.0 and I was using Hilt androidx.hilt:hilt-work:1.0.0 to use @HiltWorker to inject fields into my worker. As long as I am using only the default params (context and workerparams) its working fine, but as soon as I add any custom fields I am getting error like:

2021-09-07 16:36:00.507 24441-26679/com.xxx.xxxx E/WM-WorkerFactory: Could not instantiate com.xxx.xxxx.workmanagers.DataSyncWorker
    java.lang.NoSuchMethodException: com.xxx.xxxx.workmanagers.DataSyncWorker.<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:244)
        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)
2021-09-07 16:36:00.507 24441-26679/com.xxx.xxxx E/WM-WorkerWrapper: Could not create Worker com.xxx.xxxx.workmanagers.DataSyncWorker

@wakaztahir
Copy link
Author

wakaztahir commented Sep 7, 2021

@forntoh I have edited my answer because your provider in the manifest was incorrect , Please update it , I didn't notice it before and also update your work manager version to greater than 2.6.0-alpha 01

@abhriyaroy Please check your work manager provider , does it exactly match with this ?

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

@wakaztahir
Copy link
Author

@abhriyaroy  It would help if you could post your work manager configuration if it does not work

@forntoh
Copy link

forntoh commented Sep 7, 2021

Hi @forntoh did you find anything to make it work?

I fixed it, but I can't even remember what I did, I will take a look at my code and update this thread later.

@forntoh
Copy link

forntoh commented Sep 7, 2021

@forntoh I have edited my answer because your provider in the manifest was incorrect , Please update it , I didn't notice it before and also update your work manager version to greater than 2.6.0-alpha 01
@abhriyaroy Please check your work manager provider , does it exactly match with the given above !

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

I actually switched to what you see here because the correct one wasn't working 😃. Anyways, I have fixed the issue. I'll post my solution soon.

@jforatier
Copy link

Hi, thanks for sharing.
Think i had same issue there. I am interested in hearing your feedback @forntoh :-)

On my side i follow this stackoverflow answer and that's correct, for me it was a dependencies issue.

I forgot to add

kapt("androidx.hilt:hilt-compiler:1.0.0")

only have

kapt("com.google.dagger:hilt-android-compiler:2.38.1")

So, here the complete depencies part fort Hilt, Work and Coroutines

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'

implementation "com.google.dagger:hilt-android:2.38.1"
kapt "com.google.dagger:hilt-android-compiler:2.38.1"
implementation("androidx.hilt:hilt-work:1.0.0")
kapt("androidx.hilt:hilt-compiler:1.0.0")

implementation "androidx.work:work-runtime:2.6.0"
implementation "androidx.work:work-runtime-ktx:2.6.0"

It can be a little messy to deal with all those depencies but we are targeting many purpose that worth the effort. 😊

@Luanpedro21
Copy link

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

    works fine! 

@mbahgojol
Copy link

mbahgojol commented Nov 22, 2021

Hi, I solve this problem
workmanager version 2.7.1
hilt version 2.38.1

in my solution, i create custom workerFactory

class MyWorkerFactory @Inject constructor(private val repository: Repository) : WorkerFactory() {
    override fun createWorker(
        appContext: Context,
        workerClassName: String,
        workerParameters: WorkerParameters
    ): ListenableWorker = MyWorker(appContext, workerParameters, repository)
}

in Application class

@HiltAndroidApp
class MainApplication : Application(), Configuration.Provider {
    @Inject
    lateinit var myWorkerFactory: MyWorkerFactory

   override fun getWorkManagerConfiguration(): Configuration = Configuration.Builder()
        .setMinimumLoggingLevel(android.util.Log.DEBUG)
        .setWorkerFactory(myWorkerFactory)
        .build()
}

in MyWorker

@HiltWorker
class MyWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParameters: WorkerParameters,
    private val repository: Repository
) : RxWorker(appContext, workerParameters) {
    override fun createWork(): Single<Result> {
        val id = inputData.getInt(PaymentLimitReceiver.ID_ORDER, 0)
        return repository.cancelOrder(id)
            .map {
                Result.success()
            }
            .doOnError {
                Result.retry()
            }
    }

}

in Manifest add this provider

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

@Orlandroid
Copy link

i have the same error the solution for me was add

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

and too this librery

implementation "androidx.startup:startup-runtime:1.0.0"

@frestoinc
Copy link

For those who still have trouble despite following the steps above, the line below apparently solved the problem:

kapt("androidx.hilt:hilt-compiler:1.0.0"

@zskamljic
Copy link

zskamljic commented Sep 7, 2022

None of the solutions helped me, still seeing the same problem. Dagger version 2.42.3, using these dependencies:

implementation "androidx.work:work-runtime-ktx:2.7.1"
implementation 'androidx.hilt:hilt-work:1.0.0'
implementation "com.google.dagger:hilt-android:2.42.3"
implementation 'androidx.hilt:hilt-navigation-compose:1.0.0'
kapt "com.google.dagger:hilt-compiler:2.42.3"
kapt 'androidx.hilt:hilt-compiler:1.0.0'

The following in AndroidManifest.xml, inside application:

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

My application class:

@HiltAndroidApp
class App : Application(), Configuration.Provider {
    @Inject
    lateinit var workerFactory: HiltWorkerFactory
    
    override fun getWorkManagerConfiguration(): Configuration {
        return Configuration.Builder() // This does get called, breakpoints here stop
            .setWorkerFactory(workerFactory)
            .build()
    }
}

The worker:

@HiltWorker
class EnrolWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val getBestFacility: GetBestFacilityUseCase,
) : CoroutineWorker(appContext, workerParams)

This happens using compose 1.3.0-beta02, kotlin 1.7.10, gradle plugin 7.3.0-rc01

Perhaps also worthy of note, I have the app package moved to build.gradle inside the android scope, but adding it back to AndroidManifest.xml did not make a difference, replacing ${applicationId} with my actual package name did not help either

@davidhowe
Copy link

davidhowe commented Sep 8, 2022

After a day of trying all alternative options, changing versions, I too cannot resolve issue. I can also replicate this same issue on googles sunflower app using latest versions.

Versions:

hilt_version = '2.43.2'
work_version = '2.8.0-alpha04'
// Project dependency
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
// App dependencies
// hilt (DI)
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation("androidx.hilt:hilt-work:1.0.0")
kapt("androidx.hilt:hilt-compiler:1.0.0")
// Workmanager
implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.work:work-runtime-ktx:$work_version"

Provider in manifest:

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

Worker:

@HiltWorker
class CustomerSyncWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val fetchCustomersUseCase: FetchCustomersUseCase,
) : CoroutineWorker(appContext, workerParams) {

App class:

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

    @Inject
    lateinit var workerFactory: HiltWorkerFactory

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

@BrianMwas
Copy link

Same situation here @davidhowe were you able to resolve this?

@Hyperson
Copy link

Hyperson commented Nov 2, 2022

Make sure you initialize your WorkManager get getting an instance and not use WorkManager.initialize(applicationContext, config).

val constraints = Constraints.Builder()
               .setRequiredNetworkType(NetworkType.CONNECTED)
               .setRequiresBatteryNotLow(false)
               .build()

           val work = PeriodicWorkRequestBuilder<MyWorker>(15, TimeUnit.MINUTES)
               .setConstraints(constraints)
               .build()

           WorkManager.getInstance(applicationContext).enqueue(work)

@BrianMwas
Copy link

BrianMwas commented Nov 3, 2022

So I was able to resolve this by changing my app module to this

@Module
@InstallIn(SingletonComponent::class)
object WorkManagerInitializer: Initializer<WorkManager> {
  @Provides
  @Singleton
  override fun create(@ApplicationContext context: Context): WorkManager {
    val entryPoint = EntryPointAccessors.fromApplication(
      context,
      WorkManagerInitializerEntryPoint::class.java
    )
    val configuration = Configuration
      .Builder()
      .setWorkerFactory(entryPoint.hiltWorkerFactory())
      .setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.INFO)
      .build()

    WorkManager.initialize(context, configuration)
    return WorkManager.getInstance(context)
  }

  override fun dependencies(): MutableList<Class<out Initializer<*>>> {
    return mutableListOf()
  }

  @InstallIn(SingletonComponent::class)
  @EntryPoint
  interface WorkManagerInitializerEntryPoint {
    fun hiltWorkerFactory(): HiltWorkerFactory
  }
}```
Then in my view model, I can set it up like the following 
`@HiltViewModel
class AuthenticationViewModel @Inject constructor(
  private val workManager: WorkManager,
): ViewModel() {}`

Then enqueue it
`workManager.enqueueUniquePeriodicWork(Constants.UPDATE_CACHE_WORKER, ExistingPeriodicWorkPolicy.UPDATE, request)`

@joaquim-og
Copy link

@provides
@singleton
override fun create(@ApplicationContext context: Context): WorkManager {
val entryPoint = EntryPointAccessors.fromApplication(
context,
WorkManagerInitializerEntryPoint::class.java
)
val configuration = Configuration
.Builder()
.setWorkerFactory(entryPoint.hiltWorkerFactory())
.setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.INFO)
.build()

WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)

}

override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return mutableListOf()
}

@Installin(SingletonComponent::class)
@entrypoint
interface WorkManagerInitializerEntryPoint {
fun hiltWorkerFactory(): HiltWorkerFactory
}

Wow man, after 3 days of struggling with why my solution doesn't work, your approach fits like a glove. Thanks for sharing it!

@bartchro
Copy link

bartchro commented Dec 19, 2022

Just going to share 1 thing I encountered but haven't seen mentioned in all of the places I found while googling this issue.

While migrating my app from Dagger 2 to Hilt, I followed the instructions very closely and still ran into a similar issue. While debugging I noticed that my injected workerFactory was null (along with all of my injected fields) at the time getWorkManagerConfiguration was called within my application class.
It turned out that I should have been putting @Inject HiltWorkerFactory before all of my other injected fields in my Application class (initially I added it to the end of my injected fields). The order of injections never caused issues for me before - not even sure what exactly happened, but my guess is that some other injected field tried to use WorkManager before all this init happened.

Might be worth explicitly documenting somewhere that it may be safer for existing applications with a number of @injects to make sure they put @Inject HiltWorkerFactory at the top to avoid unexpected behaviour :)

@Himanshu-381
Copy link

==> AndroidManifest:

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

==> Application Class:

@entrypoint
@Installin(SingletonComponent::class)
interface HiltWorkerFactoryEntryPoint {
fun workerFactory(): HiltWorkerFactory
}

override fun getWorkManagerConfiguration() =
    Configuration.Builder()
        .setExecutor(Dispatchers.Default.asExecutor())
        .setWorkerFactory(EntryPoints.get(this, HiltWorkerFactoryEntryPoint::class.java).workerFactory())
        .setTaskExecutor(Dispatchers.Default.asExecutor())
        .build()

=> Application onCreate() {
WorkManager.initialize(this, workManagerConfiguration)
}

Finally getting success above code. Now initialize workmanager perfectly.

@DMaendeleo
Copy link

==> AndroidManifest:

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

==> Application Class:

@entrypoint @Installin(SingletonComponent::class) interface HiltWorkerFactoryEntryPoint { fun workerFactory(): HiltWorkerFactory }

override fun getWorkManagerConfiguration() =
    Configuration.Builder()
        .setExecutor(Dispatchers.Default.asExecutor())
        .setWorkerFactory(EntryPoints.get(this, HiltWorkerFactoryEntryPoint::class.java).workerFactory())
        .setTaskExecutor(Dispatchers.Default.asExecutor())
        .build()

=> Application onCreate() { WorkManager.initialize(this, workManagerConfiguration) }

Finally getting success above code. Now initialize workmanager perfectly.

Creating CUSTOM WORKER-FACTORY was the only thing that worked for me.
Got the bug in prod, and fixed after several days. this helped a lot. @Himanshu-381
hiltVersion = "2.44.2"
workVersion = "2.8.0"

@YohannesTz
Copy link

YohannesTz commented Apr 27, 2023

I have followed all of the steps and answers provided but this answer from this question saved my day!

@mxkmn
Copy link

mxkmn commented Dec 14, 2023

The answer #2601 (comment) helped me. The only solution on the whole internet.

@hadiyarajesh
Copy link

If everything else fails, and it still doesn't work, make sure you do not call WorkManager.initialize yourself (Got from this S/O answer)

@Provides
@Singleton
override fun create(@ApplicationContext context: Context): WorkManager {
    // val configuration = Configuration.Builder().build()
    // WorkManager.initialize(context, configuration)
    return WorkManager.getInstance(context)
}

@Dimezis
Copy link

Dimezis commented Feb 12, 2024

For those who still have this problem.
Check that you're adding kapt("androidx.hilt:hilt-compiler:1.0.0") and not just hilt-android-compiler.
Those are different dependencies and worker injection works only with the former

@hearan-won
Copy link

For those who are using ksp, make sure to add both of these compilers. This fixed it for me.

ksp("androidx.hilt:hilt-compiler:1.2.0")
ksp("com.google.dagger:hilt-compiler:2.50")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests