I'm facing a problem with work manager. I want to pass a dao to work manager constructor and follow all the implementations but can't access the dao.
java.lang.NoSuchMethodException: com.itmedicus.pdmderma.worker.DermaWorker. [class android.content.Context, class androidx.work.WorkerParameters]
Worker Class
#HiltWorker
class DermaWorker #AssistedInject constructor(
#Assisted appContext: Context,
#Assisted params: WorkerParameters,
private val dermaDao: DermaDao
): CoroutineWorker ( appContext,params)
{
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
DamiData.addDermaList()
val list = DamiData.dermaList
for (derma in list) {
dermaDao.insertDermaContent(derma)
}
Result.success()
} catch (ex: Exception) {
Result.failure()
}
}
}
Application Class
#HiltAndroidApp
class PdmDerma : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
companion object {
lateinit var appContext: Context
}
override fun onCreate() {
super.onCreate()
appContext = applicationContext
plant(Timber.DebugTree())
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder().setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.build()
}
}
Manifest
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
HiltWorkerModule
#Module
#InstallIn(SingletonComponent::class)
object WorkManagerInitializer : Initializer<WorkManager> {
#Provides
#Singleton
override fun create(#ApplicationContext context: Context): WorkManager {
val configuration = Configuration.Builder().build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): List<Class<out Initializer<*>>> {
// No dependencies on other libraries.
return emptyList()
}
}
Build.gradle
//Dagger Hilt
implementation 'com.google.dagger:hilt-android:2.42'
kapt 'com.google.dagger:hilt-compiler:2.42'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation 'androidx.hilt:hilt-work:1.0.0'
implementation "androidx.startup:startup-runtime:1.1.1"
// work manager
implementation "androidx.work:work-runtime-ktx:2.7.1"
Where is your factory class? Like this?
#AssistedFactory
interface Factory {
fun create(appContext: Context, params: WorkerParameters, dermaDao: DermaDao): DermaWorker
}
}
Yes.I solved the issue. I'm trying to init workmanager from a viewmodel.In the viewmodel constructor, I'm passing private val workManager : WorkManager.But in this process, I can't init the workmanager.So I'm keeping the AndroidViewModel for context & manually creating the workmanager dependency. Here is my solving code.
#HiltViewModel
class DermaViewModel #Inject constructor(
private val dermaRepository: DermaRepository,
application
) : AndroidViewModel(context) {
init {
initWorkManager()
}
private fun initWorkManager() {
val request: WorkRequest = OneTimeWorkRequestBuilder<DermaWorker>()
.build()
val workManager = WorkManager.getInstance(context)
workManager.enqueue(request)
}
}
Related
I have a repository which I am scoping to a ViewModel via Hilt.
I also inject this repository in a Worker which is singleton scoped. The worker is Singleton scoped as this is the typical case I have seen in WorkManager examples.
This leads to a Missing Binding hilt error when building the app. I believe this is because when I make my repository ViewModelScoped it is no longer available for my workers which are Singleton scoped.
My WorkManager is only needed when I need to do the work.
Is it correct to have Workmanager and its workers Singleton scoped? Do i need to change the scope of the workers in order to avoid the repository missing binding error?
Repository Injection
#Module
#InstallIn(ViewModelComponent::class)
abstract class BindFirestore {
#Binds
#ViewModelScoped
abstract fun bindFirestoreRepository(firestoreRepository: FirestoreRepository): IFirestoreRepository
}
#Singleton
class FirestoreRepository #Inject constructor(
private val firestoreDatasource: IFirestoreDatasource,
) : IFirestoreRepository { ... }
WorkManager Injection
#Module
#InstallIn(SingletonComponent::class)
object WorkManagerModule {
#Singleton
#Provides
fun provideWorkManagerConfiguration(
addWorkerFactories: AddWorkerFactories
): Configuration {
return Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.setWorkerFactory(addWorkerFactories)
.build()
}
}
#Singleton
class AddWorkerFactories #Inject constructor(
firestore: IFirestoreRepository,
) : DelegatingWorkerFactory() {
init {
addFactory(
MyFactoryWorker(
firestore,
))
}
}
class MyFactoryWorker(
val firestore: IFirestoreRepository,
): WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters,
): ListenableWorker? {
return when (workerClassName) {
WorkerGetData::class.java.name -> WorkerGetData(
appContext,
workerParameters,
firestore,
)
else -> null
}
}
}
#HiltWorker
class WorkerGetData #AssistedInject constructor(
#Assisted val context: Context,
#Assisted workerParameters: WorkerParameters,
val firestore: IFirestoreRepository,
): CoroutineWorker(context, workerParameters) { ... }
I am trying to implement instrumented test for my custom Work Manager which uses Hilt's #AssistedInject.
My Work Manager performs perfect in an app but when I am trying to test it according to Google's work manager integration test guide I'm getting a error:
WM-WorkerFactory: java.lang.NoSuchMethodException: com.android.wmapp.data.SyncWorker. [class android.content.Context, class androidx.work.WorkerParameters]
I've turned off default initialization in AndroidManifest.xml:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
Implemented Configuration.Provider in my Application class:
#HiltAndroidApp
class MyApplication : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder().setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(android.util.Log.DEBUG).build()
}
}
Set up my Worker class:
#HiltWorker
class SyncWorker #AssistedInject constructor(
#Assisted applicationContext: Context,
#Assisted workerParams: WorkerParameters,
private val someRepository: SomeRepository,
private val dispatchers: Dispatchers,
) : CoroutineWorker(applicationContext, workerParams) {
override suspend fun doWork(): Result {
val result = withContext(dispatchers.IO) {
someRepository.synchronize()
}
return if (result) Result.success() else Result.retry()
}
}
My library's test build.gradle configuration:
// Test
testImplementation 'junit:junit:4.+'
androidTestImplementation "androidx.work:work-testing:2.7.1"
androidTestImplementation "com.google.dagger:hilt-android-testing:2.43"
kaptAndroidTest "com.google.dagger:hilt-android-compiler:2.43"
kaptAndroidTest 'com.google.dagger:hilt-compiler:2.43'
kaptAndroidTest 'androidx.hilt:hilt-compiler:1.0.0'
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test:rules:1.4.0"
androidTestImplementation 'androidx.hilt:hilt-work:1.0.0'
implementation 'androidx.test.ext:junit-ktx:1.1.3'
My instrumented test:
#HiltAndroidTest
class SyncWorkerTest {
#get:Rule
val hiltRule = HiltAndroidRule(this)
private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
#Before
fun setUp() {
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
#Test
fun testDoWork() {
val request = SyncWorker.startSyncJob()
val workManager = WorkManager.getInstance(context)
val testDriver = WorkManagerTestInitHelper.getTestDriver(context)!!
workManager.enqueueUniqueWork(
SyncWorker.SyncWorkName,
ExistingWorkPolicy.REPLACE,
request,
)
val preRunWorkInfo = workManager.getWorkInfoById(request.id).get()
Assert.assertEquals(WorkInfo.State.ENQUEUED, preRunWorkInfo.state)
testDriver.setAllConstraintsMet(request.id)
}
}
Implemented my own JUnitRunner and specified it in my module's build.gradle:
class YALTestRunner : AndroidJUnitRunner() {
override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
testInstrumentationRunner "com.android.wmapp.YALTestRunner"
What have I missed with my WorkManager's test implementation?
I think you have the wrong config in the manifets. Please check here. It should be a bit different for version 2.6 and later:
https://developer.android.com/topic/libraries/architecture/workmanager/advanced/custom-configuration#remove-default
I would say that the issue might be caused by missing HiltWorkerFactory in configuration object provided to initializeTestWorkManager method. Please try to modify your instrumented test:
#HiltAndroidTest
class SyncWorkerTest {
#Inject
lateinit var workerFactory: HiltWorkerFactory
#get:Rule
val hiltRule = HiltAndroidRule(this)
private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
#Before
fun setUp() {
hiltRule.inject()
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.setWorkerFactory(workerFactory)
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
#Test
fun testDoWork() {
val request = SyncWorker.startSyncJob()
val workManager = WorkManager.getInstance(context)
val testDriver = WorkManagerTestInitHelper.getTestDriver(context)!!
workManager.enqueueUniqueWork(
SyncWorker.SyncWorkName,
ExistingWorkPolicy.REPLACE,
request,
)
val preRunWorkInfo = workManager.getWorkInfoById(request.id).get()
Assert.assertEquals(WorkInfo.State.ENQUEUED, preRunWorkInfo.state)
testDriver.setAllConstraintsMet(request.id)
}
}
I'm trying to use WorkManager for a periodic to retrieve notifications count from the API, the problem is that I'm using hilt for the dependency injection therefore I can't inject my repository with "#Inject" even if i used #AndroidEnteryPoint. And how I can observe my retrieved data inside the "doWork" function.
class NotificationsCountWorker
(
val context: Context,
workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
#Inject lateinit var repo: RepositoryImpl
override suspend fun doWork(): Result {
Log.d(TAG, "doWork")
//subscribeObserver()
return try {
withContext(Dispatchers.IO) {
try {
Log.d(TAG, "inside with context")
repo.notificationsCount().onEach {
Log.d(TAG, "item; $it")
}.launchIn(this)
Log.d(TAG, "notif count ")
Result.success()
} catch (e: Exception){
Log.d(TAG, "exception ${e.message}")
Result.failure()
}
}
}catch (e:Exception){
Log.d(TAG, "exception ${e.message}")
Result.failure()
}
}
companion object {
private const val TAG = "NotificationsWorkManger"
const val NOTIFICATIONS_COUNT_WORK_MANGER_ID = "automatic_notifications_count_manger"
fun reminder() {
val periodicRefreshRequest = PeriodicWorkRequest.Builder(
NotificationsCountWorker::class.java, // Your worker class
15, // repeating interval
TimeUnit.MINUTES
)
val periodicWorkRequest: PeriodicWorkRequest = periodicRefreshRequest
.build()
WorkManager.getInstance(getApplication()).enqueueUniquePeriodicWork(
NOTIFICATIONS_COUNT_WORK_MANGER_ID,
ExistingPeriodicWorkPolicy.REPLACE,
periodicWorkRequest
)
}
}
}
You need to inject your dependencies through a constructor. In order to enable injection to a Worker with Hilt you need to do the following.
First, annotate your Worker, its constructor and constructor arguments as such:
#HiltWorker
class MyWorker #AssistedInject constructor(
#Assisted context: Context,
#Assisted workerParameters: WorkerParameters,
private val repository: Repository
) : CoroutineWorker(context, workerParameters)
You should only annotate context and workerParameters as #Assisted. All your other dependencies are resolved by Hilt, they must be installed in SingletonComponent or be unscoped.
Then inject HiltWorkerFactory to your main Application-derived class and make this class implement the Configuration.Provider interface like this:
#HiltAndroidApp
class MainApplication : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
The final step is to disable default WorkManager initialization. To do this, insert these lines to your AndroidManifest.xml if you're using WorkManager of version 2.6.0-alpha01 or higher:
<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>
or
<!-- If you want to disable android.startup completely. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove">
</provider>
If you're using older versions, you should add this:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
Hy
I recently migarted my projekt from dagger 2 to Hilt. Everything went well, but when I modified my WorkManager class, since my worker hasn't done anything.
In logcat I found this error message: WM-WorkerFactory: Could not instantiate hu.crm.crmapp.workmanager.SynchronizationWorker
java.lang.NoSuchMethodException: hu.crm.crmapp.workmanager.SynchronizationWorker. [class android.content.Context, class androidx.work.WorkerParameters]
First of all, I checked all of things, that I found in stackoverflow, so I deleted thw workmanager provider from manifest.
The Sync,and PrefManager dependies I also provided, but I don't copy that bunch of code here.
My Woker class:
#HiltWorker
class SynchronizationWorker #AssistedInject constructor(
private val sync: Sync,
private val prefManager: PrefManager,
#Assisted private val context: Context,
#Assisted workerParams: WorkerParameters
) : Worker(context, workerParams) {
private val countDownLatch = CountDownLatch(1)
override fun doWork(): Result {
val notificationHelper = NotificationHelper(context)
var workResult: Result = Result.success()
//doThings
}
My Application class:
#HiltAndroidApp
class CrmApp : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
#Inject
lateinit var errorLogDao: ErrorLogDao
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
MultiDex.install(this)
}
override fun onCreate() {
super.onCreate()
BuildTypeInitializations.init(this)
}
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
}
And there is the call of Worker class
val constraint =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED).build()
val synchronizationWorker =
OneTimeWorkRequest.Builder(SynchronizationWorker::class.java)
.setConstraints(constraint)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS
)
.build()
WorkManager.getInstance(requireContext()).enqueue(synchronizationWorker)
Thanks for the help.
I encountered the same problem and error when I wanted to inject constructor parameters in the Workmanager with the Dagger-Hilt. Follow these steps to inject constructor parameters in the Workmanager with Hilt:
Step 1: 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>
(As you've stated, you have already done this part)
Step 2: In your Application class insert this code:
#HiltAndroidApp
class ImageSearchApplication : Application(), Configuration.Provider{
#Inject lateinit var workerFactory: MyWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setMinimumLoggingLevel(android.util.Log.DEBUG)
.setWorkerFactory(workerFactory)
.build()
}
Step 3: Now create this class called MyWorkerFactory like this:
class MyWorkerFactory #Inject constructor (private val repository: UnsplashRepository) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
// This only handles a single Worker, please don’t do this!!
// See below for a better way using DelegatingWorkerFactory
return MyWorker(appContext, workerParameters, repository)
}
}
That's it! Note that, pass and inject all the parameters you need in the Worker class. I needed only my repository so I defined and injected it.
I'm trying to set up work manager to do some work and I'm having trouble initializing it.
Im using KOIN workmaanger dsl
implementation "org.koin:koin-androidx-workmanager:2.2.0-rc-4"
and my worker class looks like this
class NotificationsScheduler(
private val dispatchers: AppCoroutineDispatchers,
private val getTaskUseCase: GetTaskUseCase,
private val context: Context,
private val workerParameters: WorkerParameters
) : Worker(context, workerParameters) {
override fun doWork(): Result {
...
}
What I've done so far is disabled default initializer
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
My worker module is set up like this
val workerModule = module {
worker { NotificationsScheduler(get(), get(), get(), get()) }
}
and it is added in list used in startKoin DSL. I've also used workManagerFactory() DSL to set up factory.
startKoin {
androidContext(this#MyApplication)
workManagerFactory()
modules(koinModules)
}
What I'm having trouble with, is that it crashes when app start with exception:
Caused by: org.koin.core.error.NoBeanDefFoundException: No definition found for class:'androidx.work.WorkerParameters'. Check your definitions!
Just take NotificationsScheduler class implements KoinComponent and inject the AppCoroutineDispatchers and GetTaskUseCase instances by inject() like this:
class NotificationsScheduler(context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters), KoinComponent {
private val dispatchers: AppCoroutineDispatchers by inject()
private val getTaskUseCase: GetTaskUseCase by inject()
}
In worker module:
val workerModule = module {
worker { OneTimeWorkRequestBuilder<AlarmNotificationHandleWorker>().run{
WorkManager.getInstance(androidContext())
.enqueueUniqueWork(UUID.randomUUID().toString()
,ExistingWorkPolicy.APPEND, this)
}
}
}
Make sure you had provided the GetTaskUseCase and AppCoroutineDispatchers instances
Updated: Koin 2.2.0 release:
implementation "org.koin:koin-androidx-workmanager:2.2.0"
Update your Worker class
class NotificationsScheduler(private val dispatchers: AppCoroutineDispatchers,private val getTaskUseCase: GetTaskUseCase,context: Context, parameters: WorkerParameters) : CoroutineWorker(context, parameters), KoinComponent {
}
And here you are:
val workerModule = module {
worker { NotificationsScheduler(get(),get(),androidContext(),get()) }
}
Thanks #p72b