Can't pass the dao dependency to android workmanager constructor - android

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

How to fix `missing binding' error when scoping a repository to a ViewModel and Worker?

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

Testing WorkManager with custom initializer and Hilt

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

how to make an API request inside workManager?

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" />

Workmanager doesn't start when I use HILT

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.

WorkManager set up with KOIN

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

Categories

Resources