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?
3 Réponses :
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
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:
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.
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.
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 unAndroidInjector
pour ceT
pour lequel vous avez ajouté@ContributesAndroidInjector
.Cela signifie qu'il y a un multi- liaison qui vous permet d'obtenir un
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):AndroidInjector
pour unT
, et c'est ce que faitHasAndroidInjector
pour vous.val Context.appComponent: AppComponent get() = (applicationContext as CustomApplication).component val component = context.appComponentet
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 votreAppComponent
.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(): SyncWorkerBien 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
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?
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.