Ich habe keine verständlichen Anleitungen für diejenigen gefunden,
Kodein
zum ersten Mal gesehen haben, und die Dokumentation ist nicht an allen Stellen transparent und konsistent.
Kodein
möchte ich die Hauptfunktionen der Bibliothek mit Ihnen teilen. Einige Bibliotheksfunktionen werden veröffentlicht, dies ist jedoch im Grunde der erweiterte Teil. Hier finden Sie alles, um normal zu beginnen und Abhängigkeiten mit
Kodein
implementieren, während Sie den Artikel lesen. Der Artikel basiert auf
Kodein 5.3.0
, da für
Kodein 6.0.0
die
Support Library 28
oder
AndroidX
und keinesfalls jeder zu ihnen wechseln wird, da viele Bibliotheken von Drittanbietern noch keine kompatiblen Versionen anbieten.

Kodein
ist eine Bibliothek zur Implementierung der Abhängigkeitsinjektion (DI). Wenn Sie mit diesem Konzept nicht vertraut sind, lesen Sie den Anfang
des Dagger2-Artikels , in dem der Autor die theoretischen Aspekte von DI kurz erläutert.
In diesem Artikel werden wir alles am Beispiel von Android betrachten, aber laut den Entwicklern verhält sich Kodein auf allen von Kotlin unterstützten Plattformen (JVM, Android, JS, Native) gleich.
Installation
Aufgrund der Tatsache, dass Java über eine
type erasure
, tritt ein Problem auf - der Compiler löscht den generischen Typ. Auf Bytecode-Ebene sind
List<String>
und
List<Date>
nur
List
. Es gibt immer noch eine Möglichkeit, Informationen über generische Typen zu erhalten, aber es wird viel kosten und nur unter JVM und Android funktionieren. In diesem Zusammenhang schlagen die
Kodein
Entwickler vor, eine von zwei Abhängigkeiten zu verwenden: Eine empfängt Informationen über verallgemeinerte Typen (
kodein-generic
) während der Arbeit und die andere nicht (
kodein-erased
). Wenn Sie beispielsweise
kodein-erased
List<String>
und
List<Date
> als
List<*>
speichern, und wenn Sie
kodein-generic
alles zusammen mit dem angegebenen Typ gespeichert,
kodein-generic
als
List<String>
und
List<Date>
jeweils.
Wie wähle ich?
Schreiben Sie
nicht unter die JVM - verwenden Sie
kodein-erased
, sonst ist es unmöglich.
Schreiben Sie unter die JVM, und das Leistungsproblem ist für Sie sehr wichtig. Sie können
kodein-erased
verwenden.
kodein-erased
Sie jedoch vorsichtig, diese Erfahrung kann im schlechten Sinne dieser Wörter unerwartet sein. Wenn Sie eine reguläre Anwendung ohne besondere Leistungsanforderungen
kodein-generic
, verwenden Sie
kodein-generic
.
Wenn Sie über die Auswirkungen von DI auf die Leistung nachdenken, wird der Großteil der Abhängigkeiten meistens einmal erstellt oder die Abhängigkeiten werden für die wiederholte Wiederverwendung erstellt. Es ist unwahrscheinlich, dass Sie mit solchen Aktionen die Leistung Ihrer Anwendung erheblich beeinträchtigen können.
Installieren Sie also:
Zuerst sollte in build.gradle unter den Repositorys jcenter () sein, wenn es nicht vorhanden ist - add.
buildscript { repositories { jcenter() } }
Fügen Sie als Nächstes im Abhängigkeitsblock eine der oben genannten grundlegenden Abhängigkeiten hinzu:
implementation "org.kodein.di:kodein-di-generic-jvm:$version"
implementation "org.kodein.di:kodein-di-erased-jvm:$version"
Da es sich um Android handelt, wird es mehr Abhängigkeiten geben. Sie können natürlich darauf verzichten, Kodein funktioniert normal, aber warum sollten Sie zusätzliche Funktionen ablehnen, die für Android nützlich sind (ich werde am Ende des Artikels darüber sprechen)? Sie haben die Wahl, aber ich schlage vor, hinzuzufügen.
Hier gibt es auch Optionen.
Erstens verwenden Sie
SupportLibrary
implementation "org.kodein.di:kodein-di-framework-android-core:$version"
Die zweite - Verwendung
implementation "org.kodein.di:kodein-di-framework-android-support:$version"
Drittens - Sie verwenden AndroidX
implementation "org.kodein.di:kodein-di-framework-android-x:$version"
Wir beginnen Abhängigkeiten zu erstellen
Mit
Dagger2
bin ich es gewohnt, Abhängigkeiten beim Start der Anwendung in der Anwendungsklasse zu erstellen und zu initialisieren.
Bei Kodein geschieht dies folgendermaßen:
class MyApp : Application() { val kodein = Kodein { } }
Abhängigkeitserklärungen beginnen immer mit
bind<TYPE>() with
Tags
Das Kodein-Abhängigkeits-Tagging ist eine ähnliche Funktion wie
Qualifier
von
Dagger2
. In
Dagger2
müssen Sie entweder einen separaten
Qualifier
@Named("someTag")
oder
@Named("someTag")
, der tatsächlich auch
Qualifier
. Das Endergebnis ist einfach: Auf diese Weise unterscheiden Sie zwei Abhängigkeiten desselben Typs. Beispielsweise müssen Sie je nach Situation den
ontext
Anwendung oder einer bestimmten
Activity
abrufen. Daher müssen Sie Tags angeben, um Abhängigkeiten dafür zu deklarieren.
Kodein
können
Kodein
eine Abhängigkeit ohne Tag deklarieren.
Kodein
die
Kodein
Wenn Sie das Tag beim Empfang der Abhängigkeit nicht angeben, erhalten wir es, die anderen müssen markiert werden, und wenn die Abhängigkeit empfangen wird, muss das Tag angegeben werden.
val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... }
Der
tag
Parameter ist vom Typ
Any
, sodass Sie mehr als nur Zeichenfolgen verwenden können. Denken Sie jedoch daran, dass Klassen, die als Tags verwendet werden, die Methoden
equals
und
hashCode
implementieren müssen. Es ist immer erforderlich, ein Tag als benanntes Argument an eine Funktion zu übergeben, unabhängig davon, ob Sie eine Abhängigkeit erstellen oder empfangen.
Arten der Abhängigkeitsinjektion
Es gibt verschiedene Möglichkeiten, Abhängigkeiten in
Kodein
bereitzustellen,
Kodein
mit dem Wesentlichen - Erstellen von Singletones. Der Singleton wird im Rahmen der erstellten Instanz von
Kodein
.
Einführung in Singleton
Beginnen wir mit einem Beispiel:
val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } }
Daher stellen wir
IMyDatabase
, hinter der eine Instanz von
RoomDb
versteckt wird. Eine Instanz von
RoomDb
wird bei der ersten Anforderung der Abhängigkeit
Kodein
erst neu
Kodein
wenn eine neue
Kodein
Instanz
Kodein
. Ein Singleton wird synchronisiert erstellt, kann jedoch bei Bedarf unsynchronisiert werden. Dies erhöht die Produktivität, aber Sie müssen die folgenden Risiken verstehen.
val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } }
Wenn Sie eine Abhängigkeitsinstanz nicht beim ersten Aufruf, sondern unmittelbar nach dem Erstellen der
Kodein
Instanz erstellen müssen, verwenden Sie eine andere Funktion:
val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } }
Ständig eine neue Instanz der Abhängigkeit erstellen
Es ist möglich, keine Singletones zu erstellen, sondern ständig, wenn auf eine Abhängigkeit zugegriffen wird, um eine neue Instanz davon zu erhalten. Hierzu wird die
provider
Funktion verwendet:
val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } }
In diesem Fall wird jedes Mal, wenn wir eine
IMainPresenter
Abhängigkeit
IMainPresenter
, eine neue Instanz von
QuantityPresenter
erstellt.
Erstellen Sie ständig eine neue Instanz der Abhängigkeit und übergeben Sie die Parameter an den Konstruktor
Sie können jedes Mal eine neue Instanz davon abrufen, wenn Sie eine Abhängigkeit hinzufügen, wie im vorherigen Beispiel, aber die Parameter zum Erstellen der Abhängigkeit angeben. Parameter können maximal
5 sein . Verwenden Sie für dieses Verhalten die
factory
Methode.
val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } }
Jedes Mal, wenn wir eine zwischengespeicherte Instanz erstellen, hängt dies von den Parametern ab
Wenn Sie den vorherigen Absatz lesen, denken Sie vielleicht, dass es schön wäre, nicht jedes Mal eine neue Instanz gemäß den übergebenen Parametern zu empfangen, sondern dieselbe Instanz der Abhängigkeit von demselben Parameter zu erhalten.
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } }
Wenn wir im obigen Beispiel zum ersten Mal die Abhängigkeit mit den Parametern
5
und
10
erstellen wir eine neue Instanz von
IntRandom(5, 10)
. Wenn wir die Abhängigkeit erneut mit denselben Parametern aufrufen, erhalten wir die zuvor erstellte Instanz. Somit wird eine
map
von Singleton mit verzögerter Initialisierung erhalten. Die Argumente sind, wie im Fall der
factory
maximal
5 .
Wie bei Singletones können Sie hier die Synchronisation deaktivieren.
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } }
Verwenden von weichen und schwachen Links in Kodein
Wenn Sie Abhängigkeiten mit
singleton
oder
multiton
bereitstellen
multiton
Sie den Referenztyp für die gespeicherte Instanz angeben. Im üblichen Fall, den wir oben betrachtet haben, wird dies die übliche
strong
Verbindung sein. Es ist jedoch möglich,
soft
und
weak
Glieder zu verwenden. Wenn Sie mit diesen Konzepten noch nicht vertraut sind,
schauen Sie hier .
Daher werden Ihre Singletones möglicherweise als Teil des Anwendungslebenszyklus neu erstellt oder nicht.
val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } }
Separater Singleton für jeden Stream
Dies ist derselbe Singleton, aber für jeden Thread, der eine Abhängigkeit anfordert, wird ein Singleton erstellt. Verwenden Sie dazu den bekannten Parameter
ref
.
val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } }
Konstanten als einbettbare Abhängigkeiten
Sie können Konstanten als Abhängigkeiten angeben. In der Dokumentation wird darauf
Kodein
, dass Sie mit
Kodein
Konstanten einfacher Typen ohne Vererbung oder Schnittstellen bereitstellen müssen, z. B. Grundelemente oder Datenklassen.
val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url"
Erstellen Sie Abhängigkeiten, ohne den Typ zu ändern
Beispielsweise möchten Sie die Abhängigkeit als Singleton bereitstellen, sie jedoch nicht hinter der Schnittstelle ausblenden. Sie können den Typ einfach nicht angeben, wenn Sie
bind
aufrufen und
from
anstelle von
with
.
val kodein = Kodein { bind() from singleton { Gson() }
Die Abhängigkeit im obigen Beispiel hat den Rückgabetyp der Funktion,
Gson
es wird eine
Gson
vom
Gson
Typ
Gson
.
Erstellen Sie Unterklassenabhängigkeiten einer Oberklasse oder Schnittstelle
Kodein
können
Kodein
Abhängigkeiten für die Nachkommen einer bestimmten Klasse oder von Klassen, die eine einzelne Schnittstelle implementieren, auf unterschiedliche Weise bereitstellen.
val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } }
Die
Animal
Klasse kann entweder eine Oberklasse oder eine Schnittstelle sein. Unter Verwendung von
.subtypes
wir
animalType
mit dem
TypeToken<*>
, von dem wir bereits die Java-Klasse
TypeToken<*>
und abhängig davon die Abhängigkeit auf unterschiedliche Weise bereitstellen können. Diese Funktion kann nützlich sein, wenn Sie
TypeToken
oder seine Ableitungen als Konstruktorparameter für eine Reihe von Fällen verwenden. Auf diese Weise können Sie auch unnötigen Code mit derselben Abhängigkeitserstellung für verschiedene Typen vermeiden.
Erstellen Sie Abhängigkeiten, die andere Abhängigkeiten als Parameter benötigen
Meistens erstellen wir nicht nur eine Klasse ohne Parameter als Abhängigkeit, sondern eine Klasse, an die wir Parameter an den Konstruktor übergeben müssen.
class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway
Um eine Klasse mit Abhängigkeiten zu erstellen, die zuvor in
Kodein
aus, einen Funktionsaufruf instance () als Parameter zu übergeben. In diesem Fall ist die Reihenfolge der Erstellung nicht wichtig.
bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() }
Anstelle von
instance()
es zu Aufrufen von
provider()
oder
factory()
. Wir werden uns diese Methoden im Abschnitt zum Abrufen und Implementieren von Abhängigkeiten genauer ansehen.
Erstellen Sie eine Abhängigkeit, indem Sie die zuvor erstellte Abhängigkeitsmethode aufrufen
Es klingt nicht sehr gut, aber Sie können die
instance<TYPE>
aufrufen, um eine Klasse
instance<TYPE>
, die wir bereits irgendwo bereitstellen, und die Methode dieser Klasse aufrufen, um eine neue Abhängigkeit zu erhalten.
bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() }
Module
Mit
Dagger2
bin ich es
Dagger2
,
Dagger2
. In
Kodein
sieht auf den ersten Blick nicht alles sehr gut aus. Sie müssen viele Abhängigkeiten direkt in der
Application
erstellen, und ich persönlich mag es nicht wirklich. Es gibt jedoch eine Lösung: Mit
Kodein
können Sie auch Module erstellen und diese bei Bedarf an den entsprechenden Stellen verbinden.
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() }
Aber seien Sie vorsichtig, Module sind nur Container, die Methoden zum Abrufen von Abhängigkeiten deklarieren. Sie selbst erstellen keine Klassen. Wenn Sie daher den Empfang der Abhängigkeit als Singleton im Modul deklarieren und dieses Modul dann in zwei verschiedene Instanzen von
Kodein
importieren, erhalten Sie zwei verschiedene Singuletts, eines pro Instanz von
Kodein
.
Außerdem muss der Name jedes Moduls eindeutig sein. Wenn Sie jedoch ein Modul aus einem anderen Projekt importieren müssen, ist es schwierig, die Eindeutigkeit des Namens zu gewährleisten. Dazu können Sie das Modul umbenennen oder seinem Namen ein Präfix hinzufügen.
import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-"))
Ich bin es gewohnt zu arbeiten, wenn die Module voneinander abhängig sind und eine Art Hierarchie bilden.
Kodein
können jedes Modul einmal in
Kodein
importieren. Wenn Sie also versuchen, zwei Module mit denselben abhängigen Modulen in ein
Kodein
zu importieren, stürzt die Anwendung ab. Die Lösung ist einfach: Sie müssen zum
importOnce(someModule)
Aufruf
importOnce(someModule)
, mit dem überprüft wird, ob das gleichnamige Modul zuvor importiert wurde, und bei Bedarf importiert werden.
In solchen Fällen stürzt die Anwendung beispielsweise ab:
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) }
Wenn der
importOnce
Aufruf jedoch bei einem zweiten Verbindungsversuch ausgeführt wird, funktioniert alles. Seien Sie aufmerksam.
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) }
Vererbung
Wenn Sie dasselbe Modul zweimal verwenden, werden unterschiedliche Abhängigkeiten erstellt. Wie steht es jedoch mit der Vererbung und dem Implementierungsverhalten, das den
Subcomponents
in
Dagger2
? Alles ist einfach, Sie müssen nur von der
Kodein
Instanz erben und erhalten Zugriff auf alle Abhängigkeiten des Elternteils im Erben.
val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() }
Neudefinition
Standardmäßig können Sie die Abhängigkeit nicht überschreiben, da Benutzer sonst verrückt werden und nach Gründen suchen, warum die Anwendung nicht ordnungsgemäß funktioniert. Dies ist jedoch mit einem zusätzlichen Parameter der
bind
. Diese Funktionalität ist beispielsweise zum Organisieren von Tests hilfreich.
val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } bind<Api>(overrides = true) with singleton { OtherApiImpl() } }
Standardmäßig können Module und ihre Abhängigkeiten bereits im
Kodein
Objekt deklarierte Abhängigkeiten nicht überschreiben. Beim Importieren eines Moduls können Sie jedoch angeben, dass vorhandene Abhängigkeiten seine Abhängigkeiten überschreiben können, und in diesem Modul können Sie bereits Abhängigkeiten angeben, die andere überschreiben können.
Es klingt nicht sehr klar, verwenden wir Beispiele. In diesen Fällen stürzt die Anwendung ab:
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) }
Dabei überschreibt die Modulabhängigkeit die im
Kodein
Objekt deklarierte Abhängigkeit.
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) }
Wenn Sie jedoch wirklich möchten und verstehen, was Sie tun, können Sie ein Modul erstellen, das bei identischen Abhängigkeiten mit dem
Kodein
Objekt diese neu definiert und die Anwendung nicht abstürzt. Wir verwenden den Parameter
allowSilentOverride
für das Modul.
val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } }
In der Dokumentation werden komplexere Situationen mit Vererbung und Neudefinition von Abhängigkeiten sowie mit dem Kopieren von Abhängigkeiten bei Erben erörtert. Diese Situationen werden hier jedoch nicht berücksichtigt.
Abhängigkeiten abrufen und einfügen
Schließlich haben wir herausgefunden, wie man Abhängigkeiten auf viele Arten deklariert. Es ist Zeit herauszufinden, wie man sie in ihre Klassen bringt.
Kodein
Entwickler teilen zwei Möglichkeiten, um Abhängigkeiten zu erhalten -
injection
und
retieval
. Kurz gesagt,
injection
ist, wenn die Klasse alle Abhängigkeiten empfängt, wenn sie erstellt wird, dh im Konstruktor, und
retrieval
ist, wenn die Klasse selbst dafür verantwortlich ist, ihre Abhängigkeiten zu erhalten.
Wenn Sie
injection
weiß Ihre Klasse nichts über
Kodein
und der Code in der Klasse ist sauberer. Wenn Sie jedoch
retrieval
, haben Sie die Möglichkeit, Abhängigkeiten flexibler zu verwalten. Beim
retrieval
alle Abhängigkeiten nur zum Zeitpunkt der ersten Berufung auf die Abhängigkeit träge erhalten.
Kodein
Methoden zur Verwendung von Abhängigkeiten
Eine Instanz der
Kodein
Klasse verfügt über drei Methoden, die eine Abhängigkeit, eine Abhängigkeitsfactory oder einen Abhängigkeitsanbieter zurückgeben -
instance()
,
factory()
bzw.
provider()
. Wenn Sie also eine Abhängigkeit über eine
factory
oder einen
provider
bereitstellen, können Sie nicht nur das Ergebnis der Funktionsausführung, sondern auch die Funktion selbst empfangen. Denken Sie daran, dass Sie Tags in allen Variationen verwenden können.
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()
Abhängigkeitsinjektion durch Konstruktor
Wie Sie bereits verstanden haben, geht es um
injection
. Zum Implementieren müssen Sie zuerst alle Abhängigkeiten der Klasse in ihren Konstruktor übernehmen und dann eine Instanz der Klasse erstellen, indem Sie
kodein.newInstance
aufrufen
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()) } }
Abhängigkeitsinjektion in nullbaren Eigenschaften
Möglicherweise wissen Sie nicht, ob eine Abhängigkeit deklariert wurde. Wenn die Abhängigkeit in der
Kodein
Instanz nicht deklariert ist, führt der Code aus dem obigen Beispiel zu einer
Kodein.NotFoundException
. Wenn Sie als Ergebnis eine
null
möchten und keine Abhängigkeit besteht, gibt es hierfür drei Hilfsfunktionen:
instanceOrNull()
,
factoryOrNull()
und
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()) } }
Holen Sie sich Abhängigkeiten innerhalb der Klasse.
Wie bereits erwähnt, ist in dem Fall, in dem der
retrieval
, die Initialisierung aller Abhängigkeiten standardmäßig verzögert. Auf diese Weise können Sie Abhängigkeiten nur dann abrufen, wenn sie benötigt werden, und Abhängigkeiten in den vom System erstellten Klassen abrufen.
Activity
,
Fragment
und andere Klassen mit ihrem eigenen Lebenszyklus, es geht nur um sie.
Um Abhängigkeiten in
Activity
zu implementieren
Activity
benötigen wir nur einen Link zu einer Instanz von Kodein, wonach wir bereits bekannte Methoden verwenden können. Tatsächlich haben Sie oben bereits Beispiele für den
retrieval
. Sie müssen lediglich eine Eigenschaft deklarieren und an eine der folgenden Funktionen delegieren:
instance()
,
factory()
oder
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()
Parameter an Fabriken übergeben
Sie haben bereits gesehen, dass es ausreicht, den Parameter
arg
der
arg
zu verwenden, um einen Parameter an die Factory zu übergeben.
Aber was ist, wenn es mehrere Parameter gibt (ich habe bereits gesagt, dass es in einer Fabrik bis zu 5 Parameter geben kann )? Sie müssen nur eine arg
Klasse an den Parameter übergeben M
, der Konstruktoren überladen hat und 2 bis 5 Argumente annehmen kann. 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))
Abhängigkeitsinitialisierung erzwingen
Wie bereits erwähnt, ist die Initialisierung standardmäßig verzögert. Sie können jedoch einen Trigger erstellen, ihn an eine Eigenschaft, mehrere Eigenschaften oder eine gesamte Instanz binden Kodein
, nachdem Sie diesen Trigger gedrückt haben, und die Abhängigkeiten werden initialisiert. 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()
Lazy Kodein Instanzerstellung
Vorher haben wir ständig explizit eine Instanz erstellt Kodein
, aber es ist möglich, die Initialisierung dieser Eigenschaft mithilfe einer Klasse zu verschieben LazyKodein
, die eine Funktion im Konstruktor übernimmt, die ein Objekt zurückgeben soll Kodein
.Dieser Ansatz kann nützlich sein, wenn beispielsweise nicht bekannt ist, ob Abhängigkeiten von einer bestimmten Kodein-Instanz überhaupt benötigt werden. 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()
Ein Aufruf von Kodein.lazy führt zu einem ähnlichen Ergebnis. 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 verzögerte Initialisierung
Für die verzögerte Initialisierung ist Kodein
ein Objekt vorhanden LateInitKodein
. Sie können dieses Objekt erstellen, die Erstellung von Eigenschaften an es delegieren und nach der Initialisierung des Objekts selbst die Eigenschaft darauf festlegen. baseKodein
Danach können Sie bereits auf die Abhängigkeiten zugreifen. val kodein = LateInitKodein() val gson: Gson by kodein.instance() kodein.baseKodein = gson.fromJson(someStr)
Ruft alle Instanzen des angegebenen Typs ab
Sie können Kodein nach einer Instanz des angegebenen Typs und allen seinen Nachkommen im Formular fragen List
. Alles ist nur innerhalb des angegebenen Tags. Um dies zu tun, gibt es Methoden 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()
Wenn Sie in das Protokoll drucken, sehen Sie dort [32767, 136.88, 4562, 12.46]. Die Abhängigkeit mit dem Tag ist nicht in der Liste enthalten.Vereinfachen Sie die Erfassung von Abhängigkeiten mithilfe der KodeinAware-Schnittstelle
Diese Schnittstelle verpflichtet Sie, die type-Eigenschaft zu überschreiben Kodein
, und bietet im Gegenzug Zugriff auf alle Funktionen, die der Instanz zur Verfügung stehen 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() }
Wie Sie sehen, können Sie jetzt einfach schreiben by allInstances()
anstatt by kodein.allInstances()
zuvor. Wir haben bereits über den Auslöser für den Empfang von Abhängigkeiten gesprochen. In der Schnittstelle können KodeinAware
Sie einen Trigger überschreiben und alle deklarierten Abhängigkeiten abrufen, wenn dieser Trigger aufgerufen wird. 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() } }
Da der Zugriff auf die Abhängigkeiten und die Instanz Kodein
verzögert ist, können Sie die Instanzinitialisierung an die Kodein
in Kotlin integrierte Funktion delegieren lazy
. Ein solcher Ansatz kann in Klassen abhängig von ihrem Kontext nützlich sein, z Activity
. class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance()
Aus den gleichen Gründen können Sie einen Modifikator verwenden 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 }
Zugriff auf Abhängigkeiten ohne Delegierung von Eigenschaften
Wenn Sie aus irgendeinem Grund keine Eigenschaftsdelegierung verwenden möchten, können Sie den direkten Zugriff über DKodein
(von direkt) verwenden. Der Hauptunterschied besteht darin, dass es keine verzögerte Initialisierung mehr gibt, die Abhängigkeit sofort zum Zeitpunkt des Aufrufs erhalten instance
wird provider
und ähnliche Funktionen. Sie DKodein
können es von einer vorhandenen Kodein-Instanz herunterladen oder von Grund auf neu erstellen. 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 kann im Framework verwendet werden KodeinAware
, und DKodein
im Framework DKodeinAware
können Sie experimentieren.Holen Sie sich Abhängigkeiten in jedem Kontext
Um Kodein
mehrere Abhängigkeiten desselben Typs von einem Objekt zu erhalten, haben wir bereits die Möglichkeit untersucht, Tags und Fabriken mit Argumenten zu verwenden. Es gibt jedoch noch eine weitere Möglichkeit: die Verwendung eines Kontexts (und dies ist nicht der Kontext in Android).Unterschiede zur Abhängigkeit vom Tag:- Ein Tag kann nicht in einer Funktion verwendet werden, in der wir eine Abhängigkeit erstellen
- Bei Verwendung von Kontext haben wir Zugriff auf die Kontextinstanz in der Funktion zur Erstellung von Abhängigkeiten
Anstelle des Kontexts können Sie häufig eine Factory mit einem Argument verwenden. Entwickler Kodein
empfehlen dies, wenn Sie sich nicht sicher sind, was Sie verwenden sollen. Der Kontext kann jedoch nützlich sein, wenn Sie beispielsweise nicht zwei Argumente für denselben Typ umwandeln können.Zum Beispiel haben Sie Activity
und Presenter
, wie Sie wollen, ein einzelnes Objekt mit Kodein
, mehrere verschiedene Arten von Abhängigkeiten auf verschiedene Weise zur Verfügung zu stellen, in Abhängigkeit von der Klasse , in der sie erhalten werden. Zu führen Activity
und Presenter
zu einer Art - Sie benötigen eine optionale Schnittstelle, und die Fabrik müssen die Art des resultierenden Arguments überprüfen. Das Schema ist nicht sehr bequem. Daher schauen wir uns an, wie der Kontext verwendet wird: 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")) }
Ein Beispiel wird natürlich über die Ohren gezogen, und in der Praxis ist es unwahrscheinlich, dass Sie auf eine solche Situation stoßen, aber dieses Beispiel zeigt, wie der Kontext funktioniert.Um eine Abhängigkeit zu deklarieren, geben Sie nicht an with provider()
, sondern with contexted<OurContextClass>().provider
wo OurContextClass
sich der Klassentyp befindet, dessen Instanz als Kontext fungiert. contexted
kann nur Anbieter oder Fabrik sein.Der Zugriff auf diesen Kontext in der Funktion, die die Abhängigkeit zurückgibt, erfolgt über eine Variable mit dem Namen context
.Um eine Abhängigkeit an einen Kontext anzuhängen, müssen Sie zuerst den Kontext für das Objekt Kodein
über die Funktion angeben on()
und dann die Abhängigkeit anfordern.In ähnlicher Weise wird der Kontext im Fall von verwendet injection
. private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } }
Android-Erweiterungen
Zu Beginn des Artikels habe ich versprochen, Erweiterungsoptionen für in Betracht zu ziehen Android
.Nichts hindert Sie daran, Kodein
es wie oben beschrieben zu verwenden, aber Sie können alles um eine Größenordnung bequemer gestalten.Eingebautes Kodein für Android
Eine sehr nützliche Sache ist ein Modul, das für Android vorbereitet ist. Um eine Verbindung herzustellen, muss die Klasse die Eigenschaft träge Application
implementieren KodeinAware
und initialisieren Kodein
(um auf die Instanz zugreifen zu können Application
). Im Gegenzug erhalten Sie eine große Anzahl deklarierter Abhängigkeiten, die Sie von der Klasse erhalten können Application
, einschließlich allem, was Sie benötigen Context
. So verbinden Sie sich - sehen Sie sich ein Beispiel an. class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication))
Wie Sie sehen können - können Sie zum Beispiel bekommen LayoutInflater
. Eine vollständige Liste der im Modul deklarierten Abhängigkeiten finden Sie hier .Wenn Sie diese Abhängigkeiten außerhalb von Android-Klassen erhalten möchten, deren Kontext bekannt ist, geben Sie den Kontext explizit an. val inflater: LayoutInflater by kodein.on(context = getActivity()).instance()
Holen Sie sich schnell Eltern Kodein durch nächstgelegene Kodein ()
Es ist einfach, in Android hängen einige Objekte von anderen ab. Auf der obersten Ebene gibt es Anwendung, unter der Aktivität und dann Fragment steht. Sie können die Aktivität implementieren KodeinAware
sowie Initialisierungsaufruf closestKodein()
und somit eine Kopie erhalten Kodein
von Application
. class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
Sie können es auch außerhalb von Android-Klassen herunterladen, benötigen jedoch einen Android-Kontext, über den Sie die Funktion aufrufen können. Wenn Sie es verwenden KodeinAware
, geben Sie auch den Kontext dafür an (überschreiben Sie die entsprechende Eigenschaft und übergeben Sie den Android-Kontext an die Funktion kcontext()
). class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() }
Erstellen Sie ein separates Kodein in Aktivität
Es kann durchaus notwendig sein, in der Aktivität vom übergeordneten Kodein zu erben und es zu erweitern. Die Lösung ist ganz einfach. class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) } }
Kodein, das eine Konfigurationsänderung durchläuft
Ja, das kannst du. Hierfür gibt es eine Funktion retainedKodein
. Bei Verwendung wird das Objekt Kodein
nach einer Konfigurationsänderung nicht neu erstellt. class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } }
Was steht im Artikel nicht?
Ich gab nicht vor, vollständig zu sein, und ich selbst verstehe einige Dinge nicht gut genug, um zu versuchen, sie zu formulieren. Hier ist eine Liste dessen, was Sie selbst lernen können, wenn Sie die Grundprinzipien kennen:- Geltungsbereich
- Instanzbindung
- Mehrfachbindung
- Bereits Rückrufe
- Externe Quelle
- Fallstricke der gelöschten Version
- Konfigurierbares Kodein
- JSR-330-Kompatibilität
Gut und Links zur Dokumentation:Vielen Dank fürs Lesen, ich hoffe, der Artikel wird Ihnen nützlich sein!