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 arg
classe au paramètre M
qui 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 LazyKodein
qui 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, Kodein
un 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, KodeinAware
vous 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 Kodein
est paresseux, vous pouvez déléguer l'initialisation de l'instance à la Kodein
fonction 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
, provider
et des fonctions similaires. Vous DKodein
pouvez 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 DKodein
dans le cadre DKodeinAware
, vous pouvez expérimenter.Obtenez des dépendances dans n'importe quel contexte
Afin d'obtenir Kodein
plusieurs 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 Kodein
recommandent 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 Activity
et 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 Activity
et 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ù OurContextClass
est le type de classe, dont une instance servira de contexte. contexted
ne 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 Kodein
via 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 Kodein
comme 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 Application
implémente KodeinAware
et initialise la propriété Kodein
paresseusement (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 Kodein
de Application
. class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
Vous 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 Kodein
ne 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!