1
votes

Existe-t-il un moyen d'utiliser des injecteurs dans une classe non (Activity, Service, Fragment, Application)

Nous utilisons Dagger2 dans notre application. J'essaie de créer une base de données de salle et j'écris le code du référentiel, mais je voudrais injecter le contexte d'application et le DAO pour la classe.

J'ai le sentiment que vous ne pouvez faire d'injection de poignard que dans les fragments, les activités, les services, les applications, etc.

Voici ce que j'ai:

class DownloadsDataRepositoryImpl : IDownloadsDataRepository, HasAndroidInjector {

      @Inject
      lateinit var androidInjector : DispatchingAndroidInjector<Any>

      @Inject
      lateinit var downloadsDao: DownloadsDao

      override fun androidInjector(): AndroidInjector<Any> = androidInjector
      init { 
         androidInjector()
      }

}

Mais je suis sûr que cela ne fonctionnera pas. Y a-t-il un moyen de le faire?


1 commentaires

Avant de vous demander si vous le pouvez, demandez si vous devriez le faire. La raison de l'utilisation de l'injection de champ sur les activités, etc., est que le framework les oblige à avoir un constructeur sans argument. Un référentiel Room doit-il avoir un constructeur sans argument? Sinon, utilisez simplement l'injection de constructeur.


3 Réponses :


1
votes

Je ne sais pas comment vous avez implémenté dagger, mais voici un exemple comment vous pouvez fournir un contexte à une classe non-activité.

Supposons que vous ayez la classe AppModule, vous pouvez donc y ajouter la méthode provideContext ():

@Module
class AppModule(app: App) {

    private var application: Application = app

    @Provides
    fun provideContext(): Context {
        return application
    }
}

et voici une classe de non-activité écrite en Kotlin:

class Utils @inject constructor (private val context: Context) { .. }

Et c'est tout, il suffit de reconstruire j


0 commentaires

2
votes

Comme indiqué, dagger-android est juste un outil pour aider à injecter des classes de framework spécifiques que vous ne pouvez pas contrôler lors de sa création.

La bonne approche consiste à utiliser un simple construction injection .

Pour être plus direct sur la façon dont vous devez l'exposer sur votre @Component , j'aurais besoin de plus de code, spécifiquement sur ce que vous avez sur votre activité / fragment, mais voici un exemple grossier (que je n'ai pas testé, s'il y a des erreurs mineures, vous pouvez les corriger en suivant le messages d'erreur du compilateur):

Tout d'abord, vous aurez un objet qui expose votre DAO. C'est probablement de la place?

@Component(
    modules = [
        RepositoryModule::class
    ]
)
interface DownloadComponent {

    fun inject(activity: DownloadActivity)

    @Component.Factory
    interface Factory {
        fun create(context: Context): DownloadComponent
    }
}

Nous allons maintenant créer un référentiel grossier construit avec l'annotation @Inject . Dagger se chargera de la construction de cet objet pour nous. Notez que je n'utilise pas dagger-android pour cela:

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

Comment l'exposer à votre activité / fragment / service nécessite plus de détails sur votre implémentation. Par exemple, s'il se trouve dans un ViewModel ou un Presenter annoté avec @Inject ou si vous accédez directement à votre activité, cela entraînera des implémentations. Sans plus de détails, je suppose que vous accédez au référentiel directement sur votre activité:

class DownloadActivity : FragmentActivity() {

    @Inject
    lateinit val repo: IDownloadsDataRepository

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DaggerDownloadComponent.factory().create(this).inject(this)
    }
}

Nous devons maintenant indiquer à Dagger comment:

  1. Liez votre DownloadsDataRepositoryImpl concrets à l'interface IDownloadsDataRepository requise par l'activité
  2. Comment fournir les dépendances pour créer DownloadsDataRepositoryImpl

Pour cela, nous aurons besoin d'un module a>:

interface IDownloadsDataRepository

class DownloadsDataRepositoryImpl @Inject constructor(
    val downloadsDao: DownloadsDao
) : IDownloadsDataRepository

Avec cela, nous pouvons terminer la dernière partie de notre puzzle, en créant le @Component:

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

Notez que je n'ai utilisé aucun code dagger-android , je ne pense pas que ce soit utile et cause plus de confusion que nécessaire. Tenez-vous en aux constructions de base de dagger2 et tout va bien. Vous pouvez implémenter 99,9% de votre application en ne comprenant que le fonctionnement de ces constructions:

@Module , @Component et @Subcomponent code >

Modifier : comme indiqué dans les commentaires, vous devrez probablement gérer correctement le scope de votre référentiel, spécialement la création de DB si vous utilisez réellement Room.


3 commentaires

Pour ce faire, vous créez une nouvelle instance de composant et n'utilisez aucune étendue. Et si j'ai besoin de fournisseurs étendus? Je ne peux sûrement pas simplement créer un composant singleton dans Activity.onCreate () alors. Cette réponse suppose que le référentiel est sans état et que l'instance de base de données n'a pas de surcharge de création (car elle serait recréée à chaque fois lors de la rotation, etc.). Bien qu'il soit vrai que oui, le QA a voulu utiliser l'injection de constructeur à la place.


Tous les points pertinents, mais je pense que l'explication des portées est un concept accessoire pour sa question. L'ajout de cela à la réponse nécessiterait des explications supplémentaires qui sont mieux expliquées dans le deuxième lien que j'ai utilisé comme référence dans ma réponse.


Comme je l'ai dit dans ma réponse, ce n'était pas censé être une implémentation fonctionnelle, mais seulement un croquis sur la façon dont le constructeur injecte son référentiel au lieu de s'appuyer sur dagger-android.



0
votes

J'ai le sentiment que vous ne pouvez faire d'injection de poignard que dans les fragments, les activités, les services, les applications, etc.

Vous aviez raison de supposer qu'avant Dagger-Android 2.20, mais pas après 2.20+.

Vous pouvez maintenant créer un @ContributesAndroidInjector pour n'importe quelle classe, ce qui générera un AndroidInjector pour ce T pour lequel vous avez ajouté @ContributesAndroidInjector.

Cela signifie qu'il y a un multi- liaison qui vous permet d'obtenir un AndroidInjector pour un T , et c'est ce que fait HasAndroidInjector pour vous.

Donc, ce qui suit a fonctionné pour moi dans un scénario différent (pour les travailleurs qui injectent des membres dans work-manager, au lieu de créer une multi-liaison et une usine):
val Context.appComponent: AppComponent
    get() = (applicationContext as CustomApplication).component

val component = context.appComponent

et

val component = (context.applicationContext as CustomApplication).component

TOUTEFOIS dans votre cas particulier, rien de tout cela n'est requis.

Dagger-Android est destiné aux classes d'injection de membres à l'aide d'un -généré sous-composant, dont vous n'avez généralement besoin que si votre type injecté est à l'intérieur d'un module différent, et par conséquent vous ne pouvez pas directement ajouter fun inject (T t) cod e> dans votre AppComponent , OU vous ne voyez pas votre AppComponent.

Dans votre cas, une simple injection de constructeur suffit, car vous possédez votre propre classe.

class CustomApplication: Application() {
    lateinit var component: AppComponent
        private set

    override fun onCreate() {
        super.onCreate()
        component = DaggerAppComponent.factory().create(this)
    }
}

Que vous pouvez lier via un module

@Component(modules = [DownloadsModule::class])
@Singleton
interface AppComponent {
    fun dataRepository(): DownloadsDataRepository

    @Component.Factory
    interface Factory {
        fun create(@BindsInstance appContext: Context): AppComponent
    }
}

Et sinon, vous créez simplement votre instance de composant à l'intérieur de Application.onCreate ()

@Module
abstract class DownloadsModule {
    @Binds
    abstract fun dataRepository(impl: DownloadsDataRepositoryImpl): IDownloadsDataRepository
}

Et

@Singleton
class DownloadsDataRepositoryImpl @Inject constructor(
    private val downloadsDao: DownloadsDao
): IDownloadsDataRepository {}

Ensuite, vous pouvez l'obtenir comme p>

@ContributesAndroidInjector
abstract fun syncWorker(): SyncWorker

Bien que techniquement, vous pouvez aussi créer une fonction d'extension

@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

1 commentaires

Je pense que vous avez oublié de lui expliquer comment exposer votre composant. S'il s'agit d'une variable privée, comment comptez-vous l'utiliser sur vos activités / fragments / etc?