Donc, d'après ce que j'ai lu, Dagger ne supporte pas encore l'injection dans Worker. Mais il existe certaines solutions de rechange, comme le suggèrent les gens. J'ai essayé de le faire de différentes manières en suivant des exemples en ligne, mais aucun ne fonctionne pour moi.
Lorsque je n'essaie pas d'injecter quoi que ce soit dans la classe Worker, le code fonctionne bien, sauf que je ne peux pas faire ce que je veux parce que j'ai besoin d'un accès à certains DAO et services. Si j'utilise @Inject sur ces dépendances, les dépendances sont nulles ou le travailleur ne démarre jamais car le débogueur n'entre même pas dans la classe Worker.
Par exemple, j'ai essayé de faire ceci:
@Component(modules = {Module.class})
public interface Component{
void inject(MyWorker myWorker);
}
@Module
public class Module{
@Provides
public MyRepository getMyRepo(){
return new myRepository();
}
}
Et dans mon ouvrier
@Inject
MyRepository myRepo;
public MyWorker() {
DaggerAppComponent.builder().build().inject(this);
}
Mais alors l'exécution n'atteint jamais le travailleur. Si je supprime le constructeur, la dépendance myRepo reste nulle.
J'ai essayé de faire beaucoup d'autres choses, mais aucune ne fonctionne. Y a-t-il même un moyen de faire ça? Merci!!
Vous devez regarder WorkerFactory , disponible à partir de 1.0.0-alpha09
.
Les solutions précédentes contenaient la possibilité de créer une variable Worker
à l'aide du constructeur par défaut 0-arg, mais à partir de 1.0.0-alpha10
, cette option n'est plus possible.
Supposons que vous avez une sous-classe Worker
appelée DataClearingWorker
et que cette classe a besoin d'une Foo
de votre graphe Dagger.
class DataClearingWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
lateinit var foo: Foo
override fun doWork(): Result {
foo.doStuff()
return Result.SUCCESS
}
}
Maintenant, vous ne pouvez pas simplement instancier directement l’une de ces instances DataClearingWorker
. Vous devez donc définir une sous-classe WorkerFactory
pouvant en créer une pour vous. et non seulement en créer un, mais aussi définir votre champ Foo
.
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
}
}
Enfin, vous devez créer une DaggerWorkerFactory
qui a accès à la Foo
. Vous pouvez le faire de la manière normale Dague.
@Provides
@Singleton
fun workerFactory(foo: Foo): WorkerFactory {
return DaggerWorkerFactory(foo)
}
Vous devrez également désactiver l'initialisation par défaut WorkManager
(qui se produit automatiquement) et l'initialiser manuellement.
Dans le AndroidManifest.xml
, vous pouvez le désactiver comme ceci:
<provider
Android:name="androidx.work.impl.WorkManagerInitializer"
Android:authorities="com.your.app.package.workmanager-init"
Android:enabled="false"
Android:exported="false"
tools:replace="Android:authorities" />
Assurez-vous de remplacer com.votre.app.package par le package de votre application actuelle. Le bloc <provider
ci-dessus va dans votre balise <application
.. il s'agit donc d'un frère de votre Activities
, Services
etc ...
Dans votre sous-classe Application
(ou ailleurs si vous préférez), vous pouvez initialiser manuellement WorkManager
.
@Inject
lateinit var workerFactory: WorkerFactory
private fun configureWorkManager() {
val config = Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
WorkManager.initialize(this, config)
}
J'utilise Dagger2 Multibindings pour résoudre ce problème.
Une approche similaire est utilisée pour injecter des objets ViewModel
(elle est bien décrite ici ). Une différence importante par rapport au cas de modèle de vue est la présence d'arguments Context
et WorkerParameters
dans le constructeur Worker
. Pour fournir ces arguments au composant de poignard intermédiaire du constructeur du travailleur, il doit être utilisé.
Annotez le constructeur de votre Worker
avec @Inject
et indiquez la dépendance souhaitée en tant qu'argument du constructeur.
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
}
}
Créez des annotations personnalisées qui spécifient la clé de l'entrée de la carte multibound de l'agent.
@MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class WorkerKey(val value: KClass<out Worker>)
Définir la liaison du travailleur.
@Module
interface HardWorkerModule {
@Binds
@IntoMap
@WorkerKey(HardWorker::class)
fun bindHardWorker(worker: HardWorker): Worker
}
Définissez le composant intermédiaire avec son générateur et son module qui fourniront les objets Context
et WorkerParameters
. Le composant doit avoir la méthode permettant de mapper les travailleurs à partir du graphe de dépendance et de contenir le module de liaison de travail parmi ses modules. De plus, le composant doit être déclaré en tant que sous-composant de son composant parent et le composant parent doit avoir la méthode pour obtenir le générateur du composant enfant.
@Module
class ArgumentsModule(private val appContext: Context,
private val workerParameters: WorkerParameters) {
@Provides
fun provideAppContext() = appContext
@Provides
fun provideWorkerParameters() = workerParameters
}
typealias WorkerMap = MutableMap<Class<out Worker>, Provider<Worker>>
@Subcomponent(modules = [
ArgumentsModule::class,
HardWorkerModule::class])
interface WorkerFactoryComponent {
fun workers(): WorkerMap
@Subcomponent.Builder
interface Builder {
fun argumentsModule(module: ArgumentsModule): Builder
fun build(): WorkerFactoryComponent
}
}
// some module of the parent component
@Module(subcomponents = [WorkerFactoryComponent::class
//, ...
])
class ParentComponentModule {
// ...
}
// parent component
@ParentComponentScope
@Component(modules = [ParentComponentModule::class
//, ...
])
interface ParentComponent {
// ...
fun workerFactoryComponent(): WorkerFactoryComponent.Builder
}
Implémentez WorkerFactory
. Il créera le composant intermédiaire, obtiendra la carte des travailleurs, trouvera le fournisseur de travailleurs correspondant et construira le travailleur demandé.
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()
.argumentsModule(ArgumentsModule(appContext, workerParameters))
.build()
.run { createWorker(workerClassName, workers()) }
}
Initialisez manuellement une WorkManager
avec une fabrique de travailleurs personnalisée (cette opération ne doit être effectuée qu'une fois par processus). N'oubliez pas de désactiver l'initialisation automatique dans le manifeste.
manifeste:
<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)
Utiliser un travailleur
val request = OneTimeWorkRequest.Builder(workerClass).build(HardWorker::class.Java)
WorkManager.getInstance().enqueue(request)
Regardez ceci talk pour plus d’informations sur les fonctionnalités de WorkManager
.
WorkManager alpha09
contient un nouveau WorkerFactory que vous pouvez utiliser pour initialiser la Worker
comme vous le souhaitez.
Worker
qui prend en compte ApplicationContext
et WorkerParams
.WorkerFactory
via Configuration
.configuration
et enregistrez la WorkerFactory
nouvellement créée.WorkManager
avec cette configuration (en supprimant la ContentProvider
qui initialise WorkManager
en votre nom).Vous devez faire ce qui suit:
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);
A partir de la version 1.0.0-beta01, voici une implémentation de l'injection de poignard avec WorkerFactory.
Le concept est tiré de cet article: https://medium.com/@nlg.tuan.kiet/bb9f474bde37 et je viens de poster ma propre implémentation étape par étape (dans Kotlin ).
===========
Qu'est-ce que cette mise en œuvre essayant de réaliser est:
Chaque fois que vous souhaitez ajouter une dépendance à un agent, vous la placez dans la classe d'agent associée.
===========
1. Ajouter une interface pour l'usine de tous les travailleurs
IWorkerFactory.kt
interface IWorkerFactory<T : ListenableWorker> {
fun create(params: WorkerParameters): T
}
2. Ajoute une simple classe Worker à une fabrique qui implémente IWorkerFactory et à la dépendance de ce travailleur.
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. Ajouter une WorkerKey pour le multi-binding de Dagger
WorkerKey.kt
@MapKey
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class WorkerKey(val value: KClass<out ListenableWorker>)
4. Ajoute un module de poignard pour le ouvrier à liaisons multiples (en réalité, associe l'usine à des liaisons multiples)
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. Placez le WorkerModule dans AppComponent. Ici, j'utilise dagger-Android pour construire la classe de composants
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. Ajoutez un utilisateur personnalisé WorkerFactory pour tirer parti de la possibilité de créer un employé depuis la version 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. Dans la classe Application, replace WorkerFactory par notre classe personnalisée:
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. N'oubliez pas de désactiver l'initialisation par défaut du gestionnaire de travaux
AndroidManifest.xml
<provider
Android:name="androidx.work.impl.WorkManagerInitializer"
Android:authorities="${applicationId}.workmanager-init"
Android:enabled="false"
Android:exported="false"
tools:replace="Android:authorities" />
C'est tout.
Chaque fois que vous souhaitez ajouter une dépendance à un travailleur, vous la placez dans la classe de travail associée (comme ici, HelloWorker).
Chaque fois que vous souhaitez ajouter un travailleur, implémentez la fabrique dans sa classe et ajoutez la fabrique du travailleur à WorkerModule pour la liaison multiple.
Pour plus de détails, comme l'utilisation d'AssistedInject pour réduire les codes standard, veuillez vous reporter à l'article que j'ai mentionné au début.