Kodein. Les bases

Je n'ai pas trouvé de guides compréhensibles pour ceux qui ont Kodein pour la première fois, et la documentation n'est pas transparente et cohérente à tous les endroits, donc je veux partager avec vous les principales fonctionnalités de la bibliothèque. Certaines fonctionnalités de la bibliothèque seront publiées, mais il s'agit essentiellement de la partie avancée. Ici vous trouverez tout pour démarrer normalement et commencer à implémenter des dépendances avec Kodein en lisant l'article. L'article est basé sur Kodein 5.3.0 , car Kodein 6.0.0 nécessite la Support Library 28 ou AndroidX et en aucun cas tout le monde n'y basculera, car de nombreuses bibliothèques tierces ne proposent pas encore de versions compatibles.


Kodein est une bibliothèque pour implémenter l'injection de dépendance (DI). Si vous n'êtes pas familier avec ce concept, lisez le début de l' article sur Dagger2 , où l'auteur explique brièvement les aspects théoriques de DI.

Dans cet article, nous considérerons tout avec l'exemple d'Android, mais, selon les développeurs, Kodein se comporte de la même manière sur toutes les plateformes prises en charge par Kotlin (JVM, Android, JS, Native).

L'installation


Du fait que Java a type erasure , un problème se pose - le compilateur efface le type générique. Au niveau du bytecode, List<String> et List<Date> sont que List . Pourtant, il reste un moyen d'obtenir des informations sur les types génériques, mais cela coûtera cher et ne fonctionnera que sur JVM et Android. À cet égard, les développeurs de Kodein suggèrent d'utiliser l'une des deux dépendances: l'une reçoit des informations sur les types génériques ( kodein-generic ) pendant le travail, et l'autre non ( kodein-erased ). Par exemple, lors de l'utilisation de la List<String> et de la List<Date > kodein-erased par kodein-erased elles seront enregistrées en tant que List<*> , et lors de l'utilisation de kodein-generic tout sera enregistré avec le type spécifié, c'est-à-dire en tant que List<String> et List<Date> respectivement.

Comment choisir?

kodein-erased pas sous la JVM - utilisez kodein-erased , sinon c'est impossible.
Écrivez sous la JVM et le problème de performances est très important pour vous - vous pouvez utiliser kodein-erased , mais attention, cette expérience peut être inattendue dans le mauvais sens de ces mots. Si vous créez une application régulière sans exigences de performances particulières, utilisez kodein-generic .

En fin de compte, si vous pensez à l'impact de DI sur les performances, alors le plus souvent, la plupart des dépendances sont créées une fois, ou les dépendances sont créées pour une réutilisation répétée, il est peu probable qu'avec de telles actions, vous puissiez affecter considérablement les performances de votre application.

Alors installez:

Le premier - dans build.gradle parmi les référentiels devrait être jcenter (), s'il n'est pas là - ajouter.

 buildscript { repositories { jcenter() } } 

Ensuite, dans le bloc des dépendances, ajoutez l'une des dépendances de base mentionnées ci-dessus:

 implementation "org.kodein.di:kodein-di-generic-jvm:$version" 

 implementation "org.kodein.di:kodein-di-erased-jvm:$version" 

Puisque nous parlons d'Android, il y aura plus de dépendances. Vous pouvez bien sûr vous en passer, Kodein fonctionnera normalement, mais pourquoi refuser des fonctionnalités supplémentaires utiles pour Android (j'en parlerai à la fin de l'article)? Le choix vous appartient, mais je propose d'ajouter.

Il existe également des options ici.

Tout d'abord, vous n'utilisez pas SupportLibrary

 implementation "org.kodein.di:kodein-di-framework-android-core:$version" 

La seconde - utilisation

 implementation "org.kodein.di:kodein-di-framework-android-support:$version" 

Troisièmement - vous utilisez AndroidX

 implementation "org.kodein.di:kodein-di-framework-android-x:$version" 

Nous commençons à créer des dépendances


À l'aide de Dagger2 , j'ai l'habitude de créer et d'initialiser des dépendances au démarrage de l'application, dans la classe Application.

Avec Kodein, cela se fait comme ceci:

 class MyApp : Application() { val kodein = Kodein { /*  */ } } 

Les déclarations de dépendance commencent toujours par

 bind<TYPE>() with 

Balises


Le balisage de dépendance Kodein est une fonctionnalité similaire en Dagger2 de fonctionnalités à Qualifier de Dagger2 . Dans Dagger2 vous devez faire un Qualifier distinct ou utiliser @Named("someTag") , qui est en fait également un Qualifier . Le résultat est simple - de cette façon, vous distinguez deux dépendances du même type. Par exemple, vous devez obtenir le ontext application ou d'une Activity spécifique en fonction de la situation, vous devez donc spécifier des balises pour cela lors de la déclaration des dépendances. Kodein permet de déclarer une dépendance sans balise, ce sera la base et si vous ne spécifiez pas la balise lors de la réception de la dépendance, nous l'obtiendrons, les autres doivent être balisés et lorsque la dépendance sera reçue, la balise devra être spécifiée.

 val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... } 

Le paramètre tag est de type Any , vous pouvez donc utiliser plus que de simples chaînes. Mais rappelez-vous que les classes utilisées comme balises doivent implémenter les méthodes equals et hashCode . Il est toujours nécessaire de passer une balise à une fonction en tant qu'argument nommé, que vous créiez ou que vous receviez une dépendance.

Types d'injection de dépendance


Il existe plusieurs façons de fournir des dépendances dans Kodein , en Kodein par l'essentiel - la création de singletones. Le singleton vivra dans le cadre de l'instance créée de Kodein .

Présentation de singleton


Commençons par un exemple:

 val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } } 

Ainsi, nous fournissons (fournissons) IMyDatabase , derrière lequel une instance de RoomDb sera cachée. Une instance de RoomDb sera créée à la première demande de la dépendance; elle ne sera pas Kodein tant qu'une nouvelle instance Kodein n'aura pas été Kodein . Un singleton est créé synchronisé, mais si vous le souhaitez, il peut être rendu non synchronisé. Cela augmentera la productivité, mais vous devez comprendre les risques qui suivent.

 val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } } 

Si vous devez créer une instance de dépendance non pas au premier appel, mais immédiatement après avoir créé l'instance Kodein , utilisez une autre fonction:

 val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } } 

Création constante d'une nouvelle instance de la dépendance


Il est possible de créer non des singletones, mais constamment lors de l'accès à une dépendance pour en obtenir une nouvelle instance. Pour cela, la fonction provider est utilisée:

 val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } } 

Dans ce cas, chaque fois que nous demandons une dépendance IMainPresenter , une nouvelle instance de QuantityPresenter sera créée.

Créez constamment une nouvelle instance de la dépendance et passez des paramètres au constructeur de la dépendance


Vous pouvez en obtenir une nouvelle instance chaque fois que vous ajoutez une dépendance, comme dans l'exemple précédent, mais spécifiez les paramètres de création de la dépendance. Les paramètres peuvent être au maximum de 5 . Pour ce comportement, utilisez la méthode d' factory .

 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } 

Chaque fois que nous créons une instance mise en cache en fonction des paramètres


En lisant le paragraphe précédent, vous pourriez penser qu'il serait bien de recevoir non pas une nouvelle instance à chaque fois en fonction des paramètres passés, mais de recevoir la même instance de la dépendance sur le même paramètre.

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } } 

Dans l'exemple ci-dessus, lorsque nous obtenons la dépendance pour la première fois avec les paramètres 5 et 10 nous créons une nouvelle instance d' IntRandom(5, 10) , lorsque nous appelons à nouveau la dépendance avec les mêmes paramètres, nous obtenons l'instance précédemment créée. Ainsi, une map de singleton avec initialisation paresseuse est obtenue. Les arguments, comme dans le cas de l' factory maximum 5 .

Comme avec les singletones, vous pouvez désactiver la synchronisation ici.

 val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } } 

Utilisation de liens souples et faibles dans Kodein


Lorsque vous fournissez des dépendances à l'aide de singleton ou de multiton vous pouvez spécifier le type de référence à l'instance stockée. Dans le cas habituel, que nous avons considéré ci-dessus - ce sera le lien strong habituel. Mais il est possible d'utiliser soft liens soft et weak . Si vous êtes nouveau dans ces concepts, jetez un œil ici .

Ainsi, vos singletones peuvent être recréés dans le cadre du cycle de vie de l'application, ou ne pas l'être.

 val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } } 

Singleton séparé pour chaque flux


Il s'agit du même singleton, mais pour chaque thread demandant une dépendance, un singleton sera créé. Pour ce faire, utilisez le paramètre familier ref .

 val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } } 

Constantes en tant que dépendances intégrables


Vous pouvez fournir des constantes en tant que dépendances. La documentation attire l'attention sur le fait qu'avec Kodein vous devez Kodein constantes de types simples sans héritage ni interfaces, par exemple des primitives ou des classes de données.

 val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url" 

Créez des dépendances sans changer de type


Par exemple, vous souhaitez fournir la dépendance en tant que singleton, mais ne la cachez pas derrière l'interface. Vous ne pouvez tout simplement pas spécifier le type lors de l'appel de bind et utilisez from de with .

 val kodein = Kodein { bind() from singleton { Gson() } 

La dépendance dans l'exemple ci-dessus aura le type de retour de la fonction, c'est-à-dire qu'une dépendance de type Gson sera Gson .

Créer des dépendances de sous-classe d'une super-classe ou d'une interface


Kodein permet de fournir des dépendances de différentes manières pour les descendants d'une classe ou de classes particulières qui implémentent une seule interface.

 val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } } 

La classe Animal peut être soit une superclasse ou une interface, en utilisant .subtypes nous obtenons un animalType type TypeToken<*> , à partir duquel nous pouvons déjà obtenir une classe Java et, selon elle, fournir une dépendance de différentes manières. Cette fonctionnalité peut être utile si vous utilisez TypeToken ou ses dérivés comme paramètre constructeur pour un certain nombre de cas. De cette manière, vous pouvez également éviter le code inutile avec la même création de dépendances pour différents types.

Créer des dépendances qui nécessitent d'autres dépendances en tant que paramètres


Le plus souvent, nous ne créons pas seulement une classe sans paramètres en tant que dépendance, mais créons une classe à laquelle nous devons passer des paramètres au constructeur.

 class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway 

Afin de créer une classe avec des dépendances qui ont été précédemment créées dans Kodein suffit de passer un appel de fonction instance () en tant que paramètres. Dans ce cas, l'ordre de création n'est pas important.

 bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() } 

Au lieu d' instance() il peut y avoir des appels à provider() ou factory() ; nous examinerons de plus près ces méthodes dans la section sur l'obtention et l'implémentation des dépendances.

Créer une dépendance en appelant la méthode de dépendance précédemment créée


Cela ne semble pas très bon, mais vous pouvez appeler l' instance<TYPE> pour obtenir une classe que nous fournissons déjà quelque part et appeler la méthode de cette classe pour obtenir une nouvelle dépendance.

 bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() } 

Modules


En utilisant Dagger2 , j'ai l'habitude de Dagger2 dépendances des Dagger2 . À Kodein , à première vue, tout ne semble pas très bon. Vous devez créer de nombreuses dépendances directement dans la classe Application , et personnellement je n'aime pas vraiment ça. Mais il existe une solution, Kodein vous permet également de créer des modules, puis de les connecter aux endroits nécessaires.

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } val kodein: Kodein = Kodein { import(appModule) bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } 

Mais attention, les modules ne sont que des conteneurs déclarant des méthodes d'obtention de dépendances; ils ne créent pas eux-mêmes de classes. Par conséquent, si vous déclarez la réception de la dépendance en tant que singleton dans le module, puis importez ce module dans deux instances différentes de Kodein , vous obtiendrez deux singlets différents, un par instance de Kodein .

De plus, le nom de chaque module doit être unique. Cependant, si vous devez importer un module d'un autre projet, il est difficile de garantir l'unicité du nom; pour cela, vous pouvez renommer le module ou ajouter un préfixe à son nom.

 import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-")) 

J'ai l'habitude de travailler lorsque les modules dépendent les uns des autres et forment une sorte de hiérarchie. Kodein pouvez importer chaque module dans Kodein une fois, par conséquent, si vous essayez d'importer deux modules qui ont les mêmes modules dépendants dans un Kodein , l'application se bloquera. La solution est simple - vous devez utiliser l'appel importOnce(someModule) pour importer, qui vérifiera si le module portant le même nom a été précédemment importé, puis importez si nécessaire.

Par exemple, dans de tels cas, l'application se bloque:

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { importOnce(appModule) } val thirdModule = Kodein.Module("third") { import(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

Mais si l'appel importOnce est sur une deuxième tentative de connexion, alors tout fonctionnera. Soyez prudent.

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val secondModule = Kodein.Module("second") { import(appModule) } val thirdModule = Kodein.Module("third") { importOnce(appModule) } val kodein: Kodein = Kodein { import(secondModule) import(thirdModule) } 

Héritage


Si vous utilisez deux fois le même module, différentes dépendances seront créées, mais qu'en est-il de l'héritage et de l'implémentation d'un comportement similaire aux sous- Subcomponents dans Dagger2 ? Tout est simple, il vous suffit d'hériter de l'instance Kodein et vous aurez accès à toutes les dépendances du parent de l'héritier.

 val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } val subKodein = Kodein { extend(kodein) //   } 

Redéfinition


Par défaut, vous ne pouvez pas remplacer la dépendance, sinon les utilisateurs deviendraient fous à la recherche de raisons pour lesquelles l'application ne fonctionne pas correctement. Mais il est possible de le faire en utilisant un paramètre supplémentaire de la fonction de bind . Cette fonctionnalité sera utile, par exemple, pour organiser les tests.

 val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } /* ... */ bind<Api>(overrides = true) with singleton { OtherApiImpl() } } 

Par défaut, les modules et leurs dépendances ne peuvent pas remplacer les dépendances déjà déclarées dans l'objet Kodein , mais lors de l'importation d'un module, vous pouvez indiquer que les dépendances existantes peuvent remplacer ses dépendances, et à l'intérieur de ce module, vous pouvez déjà spécifier des dépendances que d'autres peuvent remplacer.

Cela ne semble pas très clair, utilisons des exemples. Dans ces cas, l'application se bloque:

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule) } 

  val appModule = Kodein.Module("app") { bind<Gson>() with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) } 

Et en cela, la dépendance du module écrase la dépendance déclarée dans l'objet Kodein .

  val appModule = Kodein.Module("app") { bind<Gson>(overrides = true) with singleton { provideGson() } } val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } import(appModule, allowOverride = true) } 

Mais si vous le voulez vraiment et que vous comprenez ce que vous faites, vous pouvez créer un module qui, s'il existe des dépendances identiques avec l'objet Kodein les redéfinira et l'application ne se bloquera pas. Nous utilisons le paramètre allowSilentOverride pour le module.

 val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } } 

La documentation traite des situations plus complexes avec héritage et redéfinition des dépendances, ainsi que de la copie des dépendances dans les héritiers, mais ces situations ne seront pas considérées ici.

Récupération et injection de dépendances


Enfin, nous avons compris comment déclarer les dépendances de plusieurs façons, il est temps de comprendre comment les faire entrer dans leurs classes.

Kodein développeurs de Kodein partagent deux façons d'obtenir des dépendances - l' injection et le retieval . En bref, l' injection est lorsque la classe reçoit toutes les dépendances lors de sa création, c'est-à-dire dans le constructeur, et la retrieval est lorsque la classe elle-même est responsable de l'obtention de ses dépendances.

Lorsque vous utilisez l' injection votre classe ne sait rien de Kodein et le code de la classe est plus propre, mais si vous utilisez la retrieval , vous avez la possibilité de gérer les dépendances de manière plus flexible. Dans le cas de la retrieval toutes les dépendances sont obtenues paresseusement, uniquement lors du premier appel à la dépendance.

Méthodes Kodein pour l'utilisation des dépendances


Une instance de la classe Kodein a trois méthodes qui renvoient une dépendance, une fabrique de dépendances ou un fournisseur de dépendances - instance() , factory() et provider() respectivement. Ainsi, si vous fournissez une dépendance à l'aide d'une factory ou d'un provider , vous pouvez recevoir non seulement le résultat de l'exécution de la fonction, mais également la fonction elle-même. N'oubliez pas que vous pouvez utiliser des balises dans toutes les variantes.

  val kodein: Kodein = Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by factory() private val random: Random by instance() private val randomProvider: () -> Random by provider() 

Injection de dépendance via le constructeur


Comme vous l'avez déjà compris, il s'agira de l' injection . Pour l'implémenter, vous devez d'abord prendre toutes les dépendances de la classe dans son constructeur, puis créer une instance de la classe en appelant kodein.newInstance

 class ProductApi(private val client: HttpClient, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instance(), instance()) } } 

Injection de dépendances dans les propriétés nullables


Il se pourrait bien que vous ne sachiez pas si une dépendance a été déclarée. Si la dépendance n'est pas déclarée dans l'instance Kodein , le code de l'exemple ci-dessus entraînera une Kodein.NotFoundException . Si vous souhaitez obtenir null en conséquence, s'il n'y a pas de dépendance, il existe trois fonctions auxiliaires pour cela: instanceOrNull() , factoryOrNull() et providerOrNull() .

 class ProductApi(private val client: HttpClient?, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instanceOrNull(), instance()) } } 

Obtenez les dépendances à l'intérieur de la classe.


Comme déjà mentionné, dans le cas où nous utilisons la retrieval , l'initialisation de toutes les dépendances est paresseuse par défaut. Cela vous permet d'obtenir des dépendances uniquement lorsqu'elles sont nécessaires et d'obtenir des dépendances dans les classes créées par le système.

Activity , Fragment et autres classes avec leur propre cycle de vie, tout tourne autour d'eux.

Pour implémenter des dépendances dans Activity nous avons seulement besoin d'un lien vers une instance de Kodein, après quoi nous pouvons utiliser des méthodes bien connues. En fait, vous avez déjà vu des exemples de retrieval ci-dessus, il vous suffit de déclarer une propriété et de la déléguer à l'une des fonctions: instance() , factory() ou provider()

 private val number: BigDecimal by kodein.instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by kodein.factory() private val random: Random? by kodein.instanceOrNull() private val randomProvider: (() -> Random)? by kodein.providerOrNull() 

Passer des paramètres aux usines


Vous avez déjà vu que pour passer un paramètre à la fabrique, il suffit d'utiliser le paramètre arg de la fonction d' instance .Mais que se passe-t-il s'il y a plusieurs paramètres (j'ai dit plus tôt qu'il peut y avoir jusqu'à 5 paramètres dans une usine )? Vous avez juste besoin de passer une argclasse au paramètre Mqui a surchargé les constructeurs et peut prendre de 2 à 5 arguments.

 val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } } val picker: IColorPicker by kodein.instance(arg = M(255, 211, 175, 215)) 

Forcer l'initialisation des dépendances


Comme ils l'ont dit - par défaut, l'initialisation est paresseuse, mais vous pouvez créer un déclencheur, le lier à une propriété, plusieurs propriétés ou une instance entière Kodein, après avoir tiré ce déclencheur et les dépendances seront initialisées.

 val myTrigger = KodeinTrigger() val gson: Gson by kodein.on(trigger = myTrigger).instance() /*...*/ myTrigger.trigger() //     Gson 

 val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() /*...*/ myTrigger.trigger() //        kodeinWithTrigger 

Création d'instance Lazy Kodein


Avant cela, nous avons constamment créé une instance de manière explicite Kodein, mais il est possible de reporter l'initialisation de cette propriété en utilisant une classe LazyKodeinqui prend une fonction dans le constructeur qui devrait retourner un objet Kodein.

Cette approche peut être utile si, par exemple, on ne sait pas si des dépendances d'une instance Kodein donnée sont nécessaires.

 val kodein: Kodein = LazyKodein { Kodein { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() //     kodein    

Un appel à Kodein.lazy conduira à un résultat similaire.

  val kodein: Kodein = Kodein.lazy { bind<BigDecimal>() with factory { value: String -> BigDecimal(value) } bind<Random>() with provider { Random() } } private val number: BigDecimal by kodein.instance(arg = "13.4") /* ... */ number.toPlainString() //     kodein    

Initialisation différée de Kodein


Pour une initialisation retardée, Kodeinun objet existe LateInitKodein. Vous pouvez créer cet objet, lui déléguer la création de propriétés et après avoir initialisé l'objet lui-même, définissez-lui la propriété baseKodein, après quoi vous pouvez déjà accéder aux dépendances.

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ gson.fromJson(someStr) 

Récupère toutes les instances du type spécifié


Vous pouvez demander à Kodein une instance du type spécifié et tous ses descendants dans le formulaire List. Tout est uniquement dans la balise spécifiée. Pour ce faire, il existe des méthodes allInstances, allProviders, allFactories.

  val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by kodein.allInstances() 

Si vous imprimez dans le journal, vous y verrez [32767, 136.88, 4562, 12.46]. La dépendance avec la balise n'est pas dans la liste.

Simplifiez l'acquisition des dépendances à l'aide de l'interface KodeinAware


Cette interface vous oblige à remplacer la propriété type Kodein, et en retour donne accès à toutes les fonctions disponibles pour l'instance Kodein.

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by allInstances() } 

Comme vous pouvez le voir, vous pouvez maintenant simplement écrire à la by allInstances()place. Au lieu de cela, by kodein.allInstances()

nous avons déjà parlé du déclencheur de réception des dépendances. Dans l'interface, KodeinAwarevous pouvez remplacer un déclencheur et obtenir toutes les dépendances déclarées lorsque ce déclencheur est appelé.

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } override val kodeinTrigger = KodeinTrigger() val numbers: List<Number> by allInstances() override fun onCreate() { super.onCreate() kodeinTrigger.trigger() } } 

Étant donné que l'accès aux dépendances et à l'instance Kodeinest paresseux, vous pouvez déléguer l'initialisation de l'instance à la Kodeinfonction intégrée dans Kotlin lazy. Une telle approche peut être utile dans les classes en fonction de leur contexte, par exemple dans Activity.

 class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance() 

Pour les mêmes raisons, vous pouvez utiliser un modificateur lateinit.

 class CategoriesActivity : Activity(), KodeinAware { override lateinit var kodein: Kodein private val myFloat: Float by instance() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) kodein = (application as MyApplication).kodein } 

Accès aux dépendances sans déléguer de propriétés


Si, pour une raison quelconque, vous ne souhaitez pas utiliser la délégation de propriété, vous pouvez utiliser l'accès direct via DKodein(à partir de direct). La principale différence sera que l'initialisation paresseuse ne sera plus, la dépendance sera obtenue immédiatement au moment de l'appel instance, provideret des fonctions similaires. Vous DKodeinpouvez l' obtenir à partir d'une instance Kodein existante ou créer à partir de zéro.

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with singleton { BigDecimal.TEN } } val directKodein: DKodein = kodein.direct val directKodein2: DKodein = Kodein.direct { bind<BigDecimal>() with singleton { BigDecimal.ONE } } val someNumber:BigDecimal = directKodein.instance() val someNumber2:BigDecimal = directKodein2.instance() 

Kodein peut être utilisé dans le cadre KodeinAware, et DKodeindans le cadre DKodeinAware, vous pouvez expérimenter.

Obtenez des dépendances dans n'importe quel contexte


Afin d'obtenir Kodeinplusieurs dépendances du même type à partir d'un même objet, nous avons déjà examiné la possibilité d'utiliser des balises et des usines avec des arguments, mais il y a encore une chose - utiliser un contexte (et ce n'est pas le contexte qui est dans Android).

Différences par rapport à la dépendance avec la balise:

  • Une balise ne peut pas être utilisée à l'intérieur d'une fonction dans laquelle nous créons une dépendance
  • Lors de l'utilisation du contexte, nous avons accès à l'instance de contexte dans la fonction de création de dépendances

Souvent, au lieu du contexte, vous pouvez utiliser une fabrique avec un argument, et les développeurs Kodeinrecommandent de le faire si vous ne savez pas quoi utiliser. Mais le contexte peut être utile, par exemple, lorsque vous ne pouvez pas convertir deux arguments dans le même type.

Par exemple, vous avez Activityet Presenter, et vous voulez, à l'aide d'un seul objet Kodein, fournir plusieurs dépendances de différents types de différentes manières, selon la classe dans laquelle elles sont reçues. Pour conduire Activityet Presenterà un type - vous avez besoin d' une interface en option, et l'usine devrez vérifier le type de l'argument résultant. Le schéma n'est pas très pratique. Par conséquent, nous regardons comment utiliser le contexte:

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with contexted<CategoriesActivity>().provider { context.getActivityBigDecimal() } bind<BigDecimal>() with contexted<CategoriesPresenter>().factory { initialValue:BigDecimal -> context.getPresenterBigDecimal(initialValue) } } } class CategoriesActivity : Activity(), AppKodeinAware { fun getActivityBigDecimal() = BigDecimal("16.34") private val activityBigDecimal: BigDecimal by kodein.on(context = this).instance() } class CategoriesPresenter : AppKodeinAware { fun getPresenterBigDecimal(initialValue: BigDecimal) = initialValue * BigDecimal.TEN private val presenterBigDecimal: BigDecimal by kodein.on(context = this).instance(arg = BigDecimal("31.74")) } 

Un exemple, bien sûr, sera tiré sur les oreilles et dans la pratique, il est peu probable que vous rencontriez une telle situation, mais cet exemple montre comment le contexte fonctionne.

Pour déclarer une dépendance, vous spécifiez non with provider(), mais with contexted<OurContextClass>().provider, où OurContextClassest le type de classe, dont une instance servira de contexte. contextedne peut être que fournisseur ou usine.

L'accès à ce contexte dans la fonction qui retourne la dépendance se fait via une variable nommée context.

Pour obtenir une dépendance attachée à un contexte, vous devez d'abord spécifier le contexte de l'objet Kodeinvia la fonction on(), puis demander la dépendance.

De même, le contexte est utilisé dans le cas de injection.

 private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } } 

Extensions Android


Au début de l'article, j'ai promis d'envisager des options d'extension pour Android.
Rien ne vous empêche de l'utiliser Kodeincomme nous l'avons vu ci-dessus, mais vous pouvez rendre tout plus commode par ordre de grandeur.

Kodein intégré pour Android


Une chose très utile est un module préparé pour Android. Pour la connecter, il est nécessaire que la classe Applicationimplémente KodeinAwareet initialise la propriété Kodeinparesseusement (pour accéder à l'instance Application). En retour, vous obtenez un grand nombre de dépendances déclarées que vous pouvez obtenir de la classe Application, y compris tout ce dont vous avez besoin Context. Comment se connecter - regardez un exemple.

 class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication)) //  } val inflater: LayoutInflater by instance() } 

Comme vous pouvez le voir - vous pouvez obtenir, par exemple LayoutInflater. Pour une liste complète des dépendances déclarées dans le module - regardez ici .

Si vous souhaitez obtenir ces dépendances en dehors des classes Android qui connaissent leur contexte, spécifiez le contexte explicitement.

 val inflater: LayoutInflater by kodein.on(context = getActivity()).instance() 

Obtenez rapidement le parent Kodein via le plus proche Kodein ()


C'est simple, sous Android, certains objets dépendent d'autres. Au niveau supérieur, il y a Application, sous laquelle se trouve Activité, puis Fragment. Vous pouvez implémenter dans Activity KodeinAware, et en tant qu'initialisation, appeler closestKodein()et ainsi obtenir une instance Kodeinde Application.

 class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() } 

closestKodeinVous pouvez également l'obtenir en dehors des classes Android, mais vous avez besoin d'un contexte Android à partir duquel vous pouvez appeler la fonction. Si vous l'utilisez KodeinAware, spécifiez également son contexte (remplacez la propriété correspondante et passez le contexte Android à la fonction kcontext()).

 class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() } 

Créer un Kodein distinct dans l'activité


Il peut être nécessaire d'hériter du parent Kodein dans l'activité et de le développer. La solution est assez simple.

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) /*   */ } } 

Kodein qui subit un changement de configuration


Oui, c'est possible. Il y a une fonction pour cela retainedKodein. Lors de son utilisation, l'objet Kodeinne sera pas recréé après un changement de configuration.

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } } 

Qu'est-ce qui n'est pas dit dans l'article?


Je n'ai pas prétendu être complet, et moi-même je ne comprends pas assez bien certaines choses pour essayer de les énoncer. Voici une liste de ce que vous pouvez apprendre par vous-même, en connaissant les principes de base:

  • Portées
  • Liaison d'instance
  • Multi-reliure
  • Rappels déjà
  • Source externe
  • Pièges de la version effacée
  • Kodein configurable
  • Compatibilité JSR-330

Eh bien et des liens vers la documentation:


Merci d'avoir lu, j'espère que l'article vous sera utile!

Source: https://habr.com/ru/post/fr431696/


All Articles