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() }  
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()  
 val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance()  myTrigger.trigger()  
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()  
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()  
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 =   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éveloppeursKodeinrecommandent 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))  
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!