Kodein. Die Grundlagen

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() } //   } val subKodein = Kodein { extend(kodein) //   } 

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 argKlasse 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() //     Gson 

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

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() //     kodein    

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    

Kodein verzögerte Initialisierung


Für die verzögerte Initialisierung ist Kodeinein 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. baseKodeinDanach können Sie bereits auf die Abhängigkeiten zugreifen.

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ 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 KodeinAwareSie 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 Kodeinverzögert ist, können Sie die Instanzinitialisierung an die Kodeinin 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 instancewird providerund ähnliche Funktionen. Sie DKodeinkö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 DKodeinim Framework DKodeinAwarekönnen Sie experimentieren.

Holen Sie sich Abhängigkeiten in jedem Kontext


Um Kodeinmehrere 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 Kodeinempfehlen 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 Activityund 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 Activityund Presenterzu 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>().providerwo OurContextClasssich der Klassentyp befindet, dessen Instanz als Kontext fungiert. contextedkann 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, Kodeines 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 Applicationimplementieren KodeinAwareund 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)) //  } val inflater: LayoutInflater by instance() } 

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 KodeinAwaresowie Initialisierungsaufruf closestKodein()und somit eine Kopie erhalten Kodeinvon Application.

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

closestKodeinSie 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 Kodeinnach 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!

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


All Articles