Dagger2: Unable to inject dependencies in WorkManager - android

So from what I read, Dagger doesn't have support for inject in Worker yet. But there are some workarounds as people suggest. I have tried to do it a number of ways following examples online but none of them work for me.
When I don't try to inject anything into the Worker class, the code works fine, only that I can't do what I want because I need access to some DAOs and Services. If I use #Inject on those dependencies, the dependencies are either null or the worker never starts i.e the debugger doesn't even enter the Worker class.
For eg I tried doing this:
#Component(modules = {Module.class})
public interface Component{
void inject(MyWorker myWorker);
}
#Module
public class Module{
#Provides
public MyRepository getMyRepo(){
return new myRepository();
}
}
And in my worker
#Inject
MyRepository myRepo;
public MyWorker() {
DaggerAppComponent.builder().build().inject(this);
}
But then the execution never reaches the worker. If I remove the constructor, the myRepo dependency remains null.
I tried doing many other things but none work. Is there even a way to do this? Thanks!!

Overview
You need to look at WorkerFactory, available from 1.0.0-alpha09 onwards.
Previous workarounds relied on being able to create a Worker using the default 0-arg constructor, but as of 1.0.0-alpha10 that is no longer an option.
Example
Let's say that you have a Worker subclass called DataClearingWorker, and that this class needs a Foo from your Dagger graph.
class DataClearingWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
lateinit var foo: Foo
override fun doWork(): Result {
foo.doStuff()
return Result.SUCCESS
}
}
Now, you can't just instantiate one of those DataClearingWorker instances directly. So you need to define a WorkerFactory subclass that can create one of them for you; and not just create one, but also set your Foo field too.
class DaggerWorkerFactory(private val foo: Foo) : WorkerFactory() {
override fun createWorker(appContext: Context, workerClassName: String, workerParameters: WorkerParameters): ListenableWorker? {
val workerKlass = Class.forName(workerClassName).asSubclass(Worker::class.java)
val constructor = workerKlass.getDeclaredConstructor(Context::class.java, WorkerParameters::class.java)
val instance = constructor.newInstance(appContext, workerParameters)
when (instance) {
is DataClearingWorker -> {
instance.foo = foo
}
// optionally, handle other workers
}
return instance
}
}
Finally, you need to create a DaggerWorkerFactory which has access to the Foo. You can do this in the normal Dagger way.
#Provides
#Singleton
fun workerFactory(foo: Foo): WorkerFactory {
return DaggerWorkerFactory(foo)
}
Disabling Default WorkManager Initialization
You'll also need to disable the default WorkManager initialization (which happens automatically) and initialize it manually.
How you do this depends on the version of androidx.work that you're using:
2.6.0 and onwards:
In AndroidManifest.xml, add:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="YOUR_APP_PACKAGE.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
Pre 2.6.0:
In AndroidManifest.xml, add:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="YOUR_APP_PACKAGE.workmanager-init"
android:enabled="false"
android:exported="false"
tools:replace="android:authorities" />
Be sure to replace YOUR_APP_PACKAGE with your actual app's package. The <provider block above goes inside your <application tag.. so it's a sibling of your Activities, Services etc...
In your Application subclass, (or somewhere else if you prefer), you can manually initialize WorkManager.
#Inject
lateinit var workerFactory: WorkerFactory
private fun configureWorkManager() {
val config = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
WorkManager.initialize(this, config)
}

2020/06 Update
Things become much easier with Hilt and Hilt for Jetpack.
With Hilt, all you have to do is
add annotation #HiltAndroidApp to your Application class
inject out-of-box HiltWorkerFactory in the field fo Application class
Implement interface Configuration.Provider and return the injected work factory in Step 2.
Now, change the annotation on the constructor of Worker from #Inject to #WorkerInject
class ExampleWorker #WorkerInject constructor(
#Assisted appContext: Context,
#Assisted workerParams: WorkerParameters,
someDependency: SomeDependency // your own dependency
) : Worker(appContext, workerParams) { ... }
That's it!
(also, don't forget to disable default work manager initialization)
===========
Old solution
As of version 1.0.0-beta01, here is an implementation of Dagger injection with WorkerFactory.
The concept is from this article: https://medium.com/#nlg.tuan.kiet/bb9f474bde37 and I just post my own implementation of it step by step(in Kotlin).
===========
What's this implementation trying to achieve is:
Every time you want to add a dependency to a worker, you put the dependency in the related worker class
===========
1. Add an interface for all worker's factory
IWorkerFactory.kt
interface IWorkerFactory<T : ListenableWorker> {
fun create(params: WorkerParameters): T
}
2. Add a simple Worker class with a Factory which implements IWorkerFactory and also with the dependency for this worker
HelloWorker.kt
class HelloWorker(
context: Context,
params: WorkerParameters,
private val apiService: ApiService // our dependency
): Worker(context, params) {
override fun doWork(): Result {
Log.d("HelloWorker", "doWork - fetchSomething")
return apiService.fetchSomething() // using Retrofit + RxJava
.map { Result.success() }
.onErrorReturnItem(Result.failure())
.blockingGet()
}
class Factory #Inject constructor(
private val context: Provider<Context>, // provide from AppModule
private val apiService: Provider<ApiService> // provide from NetworkModule
) : IWorkerFactory<HelloWorker> {
override fun create(params: WorkerParameters): HelloWorker {
return HelloWorker(context.get(), params, apiService.get())
}
}
}
3. Add a WorkerKey for Dagger's multi-binding
WorkerKey.kt
#MapKey
#Target(AnnotationTarget.FUNCTION)
#Retention(AnnotationRetention.RUNTIME)
annotation class WorkerKey(val value: KClass<out ListenableWorker>)
4. Add a Dagger module for multi-binding worker (actually multi-binds the factory)
WorkerModule.kt
#Module
interface WorkerModule {
#Binds
#IntoMap
#WorkerKey(HelloWorker::class)
fun bindHelloWorker(factory: HelloWorker.Factory): IWorkerFactory<out ListenableWorker>
// every time you add a worker, add a binding here
}
5. Put the WorkerModule into AppComponent. Here I use dagger-android to construct the component class
AppComponent.kt
#Singleton
#Component(modules = [
AndroidSupportInjectionModule::class,
NetworkModule::class, // provides ApiService
AppModule::class, // provides context of application
WorkerModule::class // <- add WorkerModule here
])
interface AppComponent: AndroidInjector<App> {
#Component.Builder
abstract class Builder: AndroidInjector.Builder<App>()
}
6. Add a custom WorkerFactory to leverage the ability of creating worker since the release version of 1.0.0-alpha09
DaggerAwareWorkerFactory.kt
class DaggerAwareWorkerFactory #Inject constructor(
private val workerFactoryMap: Map<Class<out ListenableWorker>, #JvmSuppressWildcards Provider<IWorkerFactory<out ListenableWorker>>>
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
val entry = workerFactoryMap.entries.find { Class.forName(workerClassName).isAssignableFrom(it.key) }
val factory = entry?.value
?: throw IllegalArgumentException("could not find worker: $workerClassName")
return factory.get().create(workerParameters)
}
}
7. In Application class, replace WorkerFactory with our custom one:
App.kt
class App: DaggerApplication() {
override fun onCreate() {
super.onCreate()
configureWorkManager()
}
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent.builder().create(this)
}
#Inject lateinit var daggerAwareWorkerFactory: DaggerAwareWorkerFactory
private fun configureWorkManager() {
val config = Configuration.Builder()
.setWorkerFactory(daggerAwareWorkerFactory)
.build()
WorkManager.initialize(this, config)
}
}
8. Don't forget to disable default work manager initialization
AndroidManifest.xml
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:enabled="false"
android:exported="false"
tools:replace="android:authorities" />
That's it.
Every time you want to add a dependency to a worker, you put the dependency in the related worker class (like HelloWorker here).
Every time you want to add a worker, implement the factory in the worker class and add the worker's factory to WorkerModule for multi-binding.
For more detail, like using AssistedInject to reduce boilerplate codes, please refer to the article I mentioned at beginning.

I use Dagger2 Multibindings to solve this problem.
The similar approach is used to inject ViewModel objects (it's described well here). Important difference from view model case is the presence of Context and WorkerParameters arguments in Worker constructor. To provide these arguments to worker constructor intermediate dagger component should be used.
Annotate your Worker's constructor with #Inject and provide your desired dependency as constructor argument.
class HardWorker #Inject constructor(context: Context,
workerParams: WorkerParameters,
private val someDependency: SomeDependency)
: Worker(context, workerParams) {
override fun doWork(): Result {
// do some work with use of someDependency
return Result.SUCCESS
}
}
Create custom annotation that specifies the key for worker multibound map entry.
#MustBeDocumented
#Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
#Retention(AnnotationRetention.RUNTIME)
#MapKey
annotation class WorkerKey(val value: KClass<out Worker>)
Define worker binding.
#Module
interface HardWorkerModule {
#Binds
#IntoMap
#WorkerKey(HardWorker::class)
fun bindHardWorker(worker: HardWorker): Worker
}
Define intermediate component along with its builder. The component must have the method to get workers map from dependency graph and contain worker binding module among its modules. Also the component must be declared as a subcomponent of its parent component and parent component must have the method to get the child component's builder.
typealias WorkerMap = MutableMap<Class<out Worker>, Provider<Worker>>
#Subcomponent(modules = [HardWorkerModule::class])
interface WorkerFactoryComponent {
fun workers(): WorkerMap
#Subcomponent.Builder
interface Builder {
#BindsInstance
fun setParameters(params: WorkerParameters): Builder
#BindsInstance
fun setContext(context: Context): Builder
fun build(): WorkerFactoryComponent
}
}
// parent component
#ParentComponentScope
#Component(modules = [
//, ...
])
interface ParentComponent {
// ...
fun workerFactoryComponent(): WorkerFactoryComponent.Builder
}
Implement WorkerFactory. It will create the intermediate component, get workers map, find the corresponding worker provider and construct the requested worker.
class DIWorkerFactory(private val parentComponent: ParentComponent) : 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) = parentComponent
.workerFactoryComponent()
.setContext(appContext)
.setParameters(workerParameters)
.build()
.workers()
.let { createWorker(workerClassName, it) }
}
Initialize a WorkManager manually with custom worker factory (it must be done only once per process). Don't forget to disable auto initialization in manifest.
manifest:
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
android:exported="false"
tools:node="remove" />
Application onCreate:
val configuration = Configuration.Builder()
.setWorkerFactory(DIWorkerFactory(parentComponent))
.build()
WorkManager.initialize(context, configuration)
Use worker
val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.java)
WorkManager.getInstance().enqueue(request)
Watch this talk for more information on WorkManager features.

In WorkManager alpha09 there is a new WorkerFactory that you can use to initialize the Worker the way you want to.
Use the new Worker constructor which takes in an ApplicationContext and WorkerParams.
Register an implementation of WorkerFactory via Configuration.
Create a configuration and register the newly created WorkerFactory.
Initialize WorkManager with this configuration (while removing the ContentProvider which initializes WorkManager on your behalf).
You need to do the following:
public DaggerWorkerFactory implements WorkerFactory {
#Nullable Worker createWorker(
#NonNull Context appContext,
#NonNull String workerClassName,
#NonNull WorkerParameters workerParameters) {
try {
Class<? extends Worker> workerKlass = Class.forName(workerClassName).asSubclass(Worker.class);
Constructor<? extends Worker> constructor =
workerKlass.getDeclaredConstructor(Context.class, WorkerParameters.class);
// This assumes that you are not using the no argument constructor
// and using the variant of the constructor that takes in an ApplicationContext
// and WorkerParameters. Use the new constructor to #Inject dependencies.
Worker instance = constructor.newInstance(appContext,workerParameters);
return instance;
} catch (Throwable exeption) {
Log.e("DaggerWorkerFactory", "Could not instantiate " + workerClassName, e);
// exception handling
return null;
}
}
}
// Create a configuration
Configuration configuration = new Configuration.Builder()
.setWorkerFactory(new DaggerWorkerFactory())
.build();
// Initialize WorkManager
WorkManager.initialize(context, configuration);

Related

Android #HiltWorker Field injection not being initialized

I have a Worker which Injects 2 classes, UriUtil and CameraUtil. Both classes are being provided by the AppModule. Even in other classes which do have inheritance with for example FileProvider, or don't inherit any class at all. The vars remain null which, if I'm correct, should be provided by the field injection.
#Module
#InstallIn(SingletonComponent::class)
object AppModule {
#Provides
#Singleton
fun uriUtil(): UriUtil { return UriUtil() }
#Provides
#Singleton
fun cameraUtil(): CameraUtil { return CameraUtil() }
}
My worker then tries to field inject, but they remain null:
#HiltWorker
class SendToBackendWorker #AssistedInject constructor(
#Assisted val context: Context,
#Assisted val workerParams: WorkerParameters
) : Worker(context, workerParams) {
...
#Inject
lateinit var uriUtil: UriUtil
#Inject
lateinit var cameraUtil: CameraUtil
...
}
As the HiltWorker documentation explained (can be found here), I've updated my Application class:
#HiltAndroidApp
class MyApp : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
My AndroidManifest contains:
<application
android:name=".MyApp"
...
Can I perhaps only use field injection in classes which inherit from the ones mentioned here?
I've found the solution. There's some additional code required for 'custom' classes, which do not inherit from the ones mentioned here.
For example, to fix the #Inject code in my Worker I had to add an #EntryPoint notation and provide it an interface:
#InstallIn(SingletonComponent::class)
#EntryPoint
interface BackendWorkerEntryPoint {
fun uriUtil(): UriUtil
fun cameraUtil(): CameraUtil
}
private fun hiltEntryPoint(appContext: Context) = EntryPointAccessors.fromApplication(
appContext,
BackendWorkerEntryPoint::class.java
)
private fun getCameraUtil(appContext: Context): CameraUtil =
hiltEntryPoint(appContext).cameraUtil()
private var cameraUtil = getCameraUtil(context)
...
For workManager library version 2.6.0-alpha01 and above you must add this code to your manifest:
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="remove" />
read warning in the bottom of this page from developers.android.com

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.

I can't inject Dao to a Repository Class, with HILT, in Android

i follow this tutorial Room Dependency Injection - MVVM To-Do List App with Flow and Architecture Components #4
Now I want to convert, a hole app that I have with Room, to this Clean Architecture.
In the tutorial Florian uses DI, to inject TaskDao into TaskViewModel, but I have a Repositories clases.
So I get to a point where the app is build without errors.
and this is my Repository:
class AnioRepository constructor(
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
//val anioDao: AnioDao
fun downloadAnios(): AniosResponse? {
GlobalScope.launch(Dispatchers.IO) {
val result = agrotrackerApi.getAnios()
if (result.isSuccessful) {
for (anio in result.body()!!){
Log.d(TAG, anio.toString())
}
}
}
return null
}
fun getAnios() {
//anioDao.getListAnios()
}
}
and this is my RepositoryModule:
#Module
#InstallIn(ApplicationComponent::class)
object RepositoryModule {
#Singleton
#Provides
fun providesAnioRepository( agrotrackerApi: AgrotrackerApi) : AnioRepository {
return AnioRepository(agrotrackerApi)
}
}
So I'm trying to add Dao class to the Repository Class, like this:
class AnioRepository constructor(
private val anioDao: AnioDao,
private val agrotrackerApi: AgrotrackerApi
) {
val TAG = "AnioRepository"
...
and then, change RepositoryModule, to match constructor...
...
fun providesAnioRepository( anioDao: AnioDao, agrotrackerApi: AgrotrackerApi) : AnioRepository
= AnioRepository(anioDao, agrotrackerApi)
...
but when I press Ctrl-F9, i get this error:
public abstract class ATMDatabase extends androidx.room.RoomDatabase {
^C:\pryectos\AndroidStudioProjects\ATMobileXKt\app\build\generated\source\kapt\debug\com\cse\atm\ATMApplication_HiltComponents.java:155:
error: [Dagger/MissingBinding] #com.cse.atm.di.ApplicationScope
kotlinx.coroutines.CoroutineScope cannot be provided without an
#Provides-annotated method. public abstract static class SingletonC
implements ATMApplication_GeneratedInjector,
^
#com.cse.atm.di.ApplicationScope kotlinx.coroutines.CoroutineScope is injected at
com.cse.atm.database.ATMDatabase.Callback(�, applicationScope)
com.cse.atm.database.ATMDatabase.Callback is injected at
com.cse.atm.di.AppModule.providesDatabase(�, callback)
com.cse.atm.database.ATMDatabase is injected at
com.cse.atm.di.AppModule.providesAnioDao(db)
com.cse.atm.database.AnioDao is injected at
com.cse.atm.di.RepositoryModule.providesAnioRepository(anioDao, �)
javax.inject.Provider<com.cse.atm.data.AnioRepository> is injected at
What means this error? What I'm missing ?
#ApplicationScope annotation is in another AppModule.kt, I dont' where is the problem.
Any help wil be appreciated!!
Best Regards
Your ATMDatabase.Callback is requesting a CoroutineScope with custom qualifier #ApplicationScope, but you are not providing such a CoroutineScope in any of your modules.
To provide a coroutine scope, the tutorial you linked adds this code around the 28-minute mark:
#Provides
#Singleton
fun provideApplicationScope() = CoroutineScope(SupervisorJob())
Since you are using the #ApplicationScope qualifier when requesting a coroutine scope, you will also need to add the qualifier to this #Provides method:
#Provides
#Singleton
#ApplicationScope
fun provideApplicationScope() = CoroutineScope(SupervisorJob())

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

Is there a way to use injectors in a non-(Activity,Service,Fragment, Application) class

We're using Dagger2 in our application. I am trying to do a room database and I am writing the repository code, but I would like to inject application context and the DAO for the class.
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
Here's what I have:
class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {
#Inject
lateinit var androidInjector : DispatchingAndroidInjector<Any>
#Inject
lateinit var downloadsDao: DownloadsDao
override fun androidInjector(): AndroidInjector<Any> = androidInjector
init {
androidInjector()
}
}
But I'm sure it's not going to work. Is there a way to do it?
As stated, dagger-android is just a tool to help injecting specific framework classes that you can't have control on it's creation.
The proper approach is to use simple construction injection.
To be more direct on how you should expose it on your #Component, I would need more code, specifically on what you have on your activity/fragment, but here is a crude example (that I did not tested, if there are minor errors, you can fix them following the compiler error messages):
First, you will have some object that exposes your DAO. Probably it's room?
#Entity(tableName = "download_table")
data class DownloadEntity(
#PrimaryKey
val key: String
)
#Dao
interface DownloadsDao {
#Query("SELECT * FROM download_table")
fun load(): List<DownloadEntity>
}
#Database(
entities = [DownloadEntity::class], version = 1
)
abstract class DownloadRoomDatabase : RoomDatabase() {
abstract val downloadsDao: DownloadsDao
}
Now we will create a crude repository that is build with #Inject annotation. Dagger will take care of building this object for us. Notice that I am not using dagger-android for it:
interface IDownloadsDataRepository
class DownloadsDataRepositoryImpl #Inject constructor(
val downloadsDao: DownloadsDao
) : IDownloadsDataRepository
How to expose it to your activity/fragment/service requires more details on your implementation. For example, if it's inside a ViewModel or a Presenter that is annotated with #Inject or you are accessing directly on your activity will result in different implementations. Without more details, I will suppose that you are accessing the repository directly on your activity:
class DownloadActivity : FragmentActivity() {
#Inject
lateinit val repo: IDownloadsDataRepository
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DaggerDownloadComponent.factory().create(this).inject(this)
}
}
Now we need to instruct Dagger on how to:
Bind your concrete DownloadsDataRepositoryImpl to the IDownloadsDataRepository interface that the activity requires
How to provide the dependencies to build DownloadsDataRepositoryImpl
For this we will need a module:
#Module
abstract class RepositoryModule {
//We will bind our actual implementation to the IDownloadsDataRepository
#Binds
abstract fun bindRepo(repo: DownloadsDataRepositoryImpl): IDownloadsDataRepository
#Module
companion object {
//We need the database to get access to the DAO
#Provides
#JvmStatic
fun provideDataBase(context: Context): DownloadRoomDatabase =
Room.databaseBuilder(
context,
DownloadRoomDatabase::class.java,
"download_database.db"
).build()
//With the database, we can provide the DAO:
#Provides
#JvmStatic
fun provideDao(db: DownloadRoomDatabase): DownloadsDao = db.downloadsDao
}
}
With this, we can finish the last part of our puzzle, creating the #Component:
#Component(
modules = [
RepositoryModule::class
]
)
interface DownloadComponent {
fun inject(activity: DownloadActivity)
#Component.Factory
interface Factory {
fun create(context: Context): DownloadComponent
}
}
Notice that I did not use any dagger-android code, I don't think it's useful and causes more confusion than necessary. Stick with basic dagger2 constructs and you are fine. You can implement 99.9% of your app only understanding how those constructs works:
#Module, #Component and #Subcomponent
Edit: As stated in the comments, probably you will need to properly manage the scope of your repository, specially the DB creation if you are actually using Room.
Not sure how you implemented dagger, but here is an example how you can provide context to non activity class.
Suppose you have AppModule class, so there you can add provideContext() method:
#Module
class AppModule(app: App) {
private var application: Application = app
#Provides
fun provideContext(): Context {
return application
}
}
and here is non activity class written in Kotlin:
class Utils #inject constructor(private val context: Context) {
..
}
And that's it, just rebuild j
I have a feeling that you can only do Dagger injection in Fragments, Activities, Services, Applications, etc.
You were correct to assume that before Dagger-Android 2.20, but not after 2.20+.
Now you can create a #ContributesAndroidInjector for any class, which will generate an AndroidInjector<T> for that T for which you added #ContributesAndroidInjector.
This means that there is a multi-binding that allows you to get an AndroidInjector<T> for a T, and this is what HasAndroidInjector does for you.
So the following worked for me in a different scenario (for member-injecting Workers in work-manager, instead of creating a multi-binding and a factory):
#Keep
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
init {
val injector = context.applicationContext as HasAndroidInjector
injector.androidInjector().inject(this)
}
#Inject
lateinit var apiService: ApiService
and
#ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker
HOWEVER in your particular case, none of this is required.
Dagger-Android is for member-injecting classes using an auto-generated subcomponent, that you typically need only if your injected type is inside a different module, and therefore you can't directly add fun inject(T t) into your AppComponent, OR you don't see your AppComponent.
In your case, simple constructor injection is enough, as you own your own class.
#Singleton
class DownloadsDataRepositoryImpl #Inject constructor(
private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}
Which you can bind via a module
#Module
abstract class DownloadsModule {
#Binds
abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}
And otherwise you just create your component instance inside Application.onCreate()
#Component(modules = [DownloadsModule::class])
#Singleton
interface AppComponent {
fun dataRepository(): DownloadsDataRepository
#Component.Factory
interface Factory {
fun create(#BindsInstance appContext: Context): AppComponent
}
}
And
class CustomApplication: Application() {
lateinit var component: AppComponent
private set
override fun onCreate() {
super.onCreate()
component = DaggerAppComponent.factory().create(this)
}
}
Then you can get it as
val component = (context.applicationContext as CustomApplication).component
Though technically you may as well create an extension function
val Context.appComponent: AppComponent
get() = (applicationContext as CustomApplication).component
val component = context.appComponent

Categories

Resources