Kodein. Die Grundlagen

Published on December 04, 2018

Kodein. Die Grundlagen

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


    KodeinIst eine Bibliothek zur Implementierung von Dependency Injection (DI). Wenn Sie mit diesem Konzept nicht vertraut sind, lesen Sie den Anfang des Artikels über Dolch2 , in dem der Autor die theoretischen Aspekte von DI kurz erklärt.

    In diesem Artikel werden wir uns alles am Beispiel von Android ansehen, 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 es in Java type erasureein Problem gibt - der Compiler löscht den generischen Typ. Auf Bytecode-Ebene List<String>und List<Date>- ist es einfach List. Es gibt immer noch eine Möglichkeit, Informationen zu verallgemeinerten Typen abzurufen, aber die Kosten sind teuer und es kann nur mit JVM und Android gearbeitet werden. In diesem Zusammenhang Kodeinschlagen die Entwickler vor, eine von zwei Abhängigkeiten zu verwenden: Eine bei der Arbeit erhält Informationen über verallgemeinerte Typen ( kodein-generic) und die andere nicht ( kodein-erased). Wenn als ein Beispiel, bei der Verwendung kodein-erased List<String>und List<Date> als gespeichert werden List<*>, und die Verwendung kodein-genericaller fortzusetzen, zusammen mit dem angegebenen Typ, das heißt, wie List<String>und List<Date>jeweils.

    Wie soll man wählen?

    Schreiben Sie nicht unter JVM - verwendenkodein-erasedSonst ist es unmöglich.
    Schreiben Sie unter der JVM, und das Problem der Leistung ist sehr wichtig für Sie - Sie können es verwenden kodein-erased, aber seien Sie vorsichtig, diese Erfahrung kann im schlechten Sinne dieser Worte unerwartet sein. Wenn Sie eine normale Anwendung ohne besondere Leistungsanforderungen erstellen, verwenden Sie kodein-generic.

    Wenn Sie über die Auswirkungen von DI auf die Leistung nachdenken, werden die meisten Abhängigkeiten meistens einmal oder zur wiederholten Wiederverwendung erstellt. Es ist unwahrscheinlich, dass Sie die Leistung Ihrer Anwendung erheblich beeinträchtigen können.

    Also, installiere:

    Zuerst - im build.gradle unter den Repositories sollte jcenter () sein, wenn es nicht da ist - füge es hinzu.

    buildscript {
        repositories {
            jcenter()
        }
    }
    

    Fügen Sie als Nächstes eine der oben genannten grundlegenden Abhängigkeiten zum Abhängigkeitsblock hinzu:

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

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

    Da wir über Android sprechen, wird es mehr Abhängigkeiten geben. Natürlich können Sie darauf verzichten, Kodein funktioniert normal, aber warum sollten Sie zusätzliche Funktionen, die für Android nützlich sind, ablehnen (ich werde sie am Ende des Artikels erläutern)? Die Wahl liegt bei Ihnen, aber ich schlage vor, hinzuzufügen.

    Hier gibt es auch Optionen.

    Die erste - Sie verwenden nichtSupportLibrary

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

    Der zweite verwendet

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

    Drittens - Sie verwenden AndroidX

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

    Beginnen Sie, Abhängigkeiten zu erstellen


    Mit habe Dagger2ich mich daran gewöhnt, Abhängigkeiten beim Anwendungsstart in der Klasse Application zu erstellen und zu initialisieren.

    Mit Kodein geht das so:

    class MyApp : Application() {
    	val kodein = Kodein { 
    	    /* Зависимости */
    	}
    }
    

    Das Deklarieren von Abhängigkeiten beginnt immer mit

    bind<TYPE>() with
    

    Tags


    Das Kennzeichnen von Abhängigkeiten in Kodein ist eine ähnliche Funktion wie Qualifierfrom Dagger2. In müssen Dagger2Sie tun oder trennen Qualifieroder verwenden @Named("someTag"), was in der Tat auch Qualifier. Das Wesentliche ist einfach: Auf diese Weise unterscheiden Sie zwei Abhängigkeiten desselben Typs. Beispielsweise müssen Sie je nach Situation Сontextbestimmte oder bestimmte Anwendungen Activityabrufen. Daher müssen Sie beim Deklarieren von Abhängigkeiten Tags angeben. KodeinWenn Sie eine Abhängigkeit ohne Tag deklarieren können, handelt es sich um eine einfache Abhängigkeit. Wenn Sie beim Empfang einer Abhängigkeit kein Tag angeben, erhalten Sie dieses. Wenn Sie den Rest mit einem Tag kennzeichnen müssen, müssen Sie das Tag angeben, wenn Sie eine Abhängigkeit erhalten.

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

    Der Parameter taghat einen Typ Any, daher können nicht nur Zeichenfolgen verwendet werden. Beachten Sie jedoch, dass die als Tags verwendeten Klassen die Methoden equalsund implementieren müssen hashCode. Sie müssen einer Funktion immer ein Tag als benanntes Argument übergeben, unabhängig davon, ob Sie eine Abhängigkeit erstellen oder empfangen.

    Arten der Abhängigkeitsinjektion


    Um KodeinAbhängigkeiten bereitzustellen, gibt es verschiedene Möglichkeiten. Beginnen wir mit den wichtigsten: Erstellen von Singletons. Der Singleton wird innerhalb der Grenzen der erstellten Kopie leben Kodein.

    Wir implementieren Singletons


    Beginnen wir mit einem Beispiel:

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

    Somit stellen wir (zur Verfügung) IMyDatabase, für die eine Kopie ausgeblendet wird RoomDb. Eine Instanz RoomDbwird bei der ersten Anforderung einer Abhängigkeit erstellt. Sie wird erst dann neu erstellt, wenn eine neue Instanz erstellt wird Kodein. Ein Singleton wird synchronisiert erstellt, aber Sie können ihn auch unsynchronisieren, wenn Sie dies wünschen. 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 eine Instanz einer Abhängigkeit nicht beim ersten Aufruf, sondern unmittelbar nach dem Erstellen einer Instanz erstellt werden muss Kodein, verwenden Sie eine andere Funktion:

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

    Erstellen Sie ständig eine neue Instanz der Abhängigkeit.


    Es ist möglich, keine Singletones zu erstellen, sondern beim Zugriff auf eine Abhängigkeit ständig deren neue Instanz zu erhalten. Verwenden Sie dazu die Funktion provider:

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

    In diesem Fall wird bei jeder Anforderung einer Abhängigkeit IMainPresentereine neue Instanz erstellt QuantityPresenter.

    Erstellen Sie ständig eine neue Instanz der Abhängigkeit und übergeben Sie die Parameter an den Abhängigkeitsdesigner


    Mit jeder Anforderung für eine Abhängigkeit können Sie wie im vorherigen Beispiel eine neue Instanz davon erhalten, aber dennoch die Parameter zum Erstellen einer Abhängigkeit angeben. Parameter können maximal 5 sein . Verwenden Sie für dieses Verhalten die Methode factory.

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

    Jedes Mal erstellen wir eine zwischengespeicherte Instanz in Abhängigkeit von den Parametern.


    Wenn Sie den vorherigen Absatz lesen, denken Sie vielleicht, dass es hilfreich wäre, nicht jedes Mal eine neue Instanz gemäß den übergebenen Parametern zu erhalten, sondern dieselbe Instanz der Abhängigkeit mit 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 eine Abhängigkeit mit Parametern erhalten 5, 10erstellen wir eine neue Instanz IntRandom(5, 10). Wenn wir die Abhängigkeit mit denselben Parametern erneut aufrufen, erhalten wir die zuvor erstellte Instanz. So stellt sich heraus, einige mapder Singleton mit fauler Initialisierung. Argumente, wie es bei factorymaximal 5 der Fall ist .

    Wie bei Singletons können Sie hier die Synchronisation deaktivieren.

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

    Soft und Weak Links in Kodein verwenden


    Wenn Sie Abhängigkeiten mit singletonoder multitonangeben, können Sie den Referenztyp für die gespeicherte Instanz angeben. In dem oben beschriebenen Fall handelt es sich um einen regulären strongLink. Aber es ist möglich zu verwenden softund weakReferenz. Wenn Sie mit diesen Konzepten nicht vertraut sind, schauen Sie hier .

    Auf diese Weise können Ihre Singletons möglicherweise innerhalb des Anwendungslebenszyklus neu erstellt werden.

    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. Hierfür verwenden wir den bereits bekannten Parameter ref.

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

    Konstanten als eingebettete Abhängigkeiten


    Es ist möglich, Konstanten als Abhängigkeiten anzugeben. In der Dokumentation wird darauf hingewiesen, dass Sie KodeinKonstanten einfachen Typs 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 eine Abhängigkeit als Singleton bereitstellen, diese jedoch nicht hinter der Schnittstelle verbergen. Sie können den Typ beim Aufrufen einfach weglassen bindund stattdessen withverwenden from.

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

    Die Abhängigkeit im obigen Beispiel hat den Rückgabetyp der Funktion, dh die Typabhängigkeit wird bereitgestellt Gson.

    Erstellen Sie Subtyp-Abhängigkeiten einer Oberklasse oder eines Interfaces


    Kodein Mit dieser Option können Sie Abhängigkeiten für die Erben einer bestimmten Klasse oder für 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) }
                    }
                }
    

    Eine Klasse Animalkann sowohl eine Superklasse als auch eine Schnittstelle sein, indem .subtypeswir animalTypeeinen Typ verwenden, TypeToken<*>von dem wir bereits eine Java-Klasse erhalten und Abhängigkeiten auf unterschiedliche Weise bereitstellen können. Diese Funktion kann nützlich sein, wenn Sie TypeTokenoder ihre Ableitungen als Parameter des Konstruktors für eine Reihe von Fällen verwenden. Auf diese Weise können Sie auch unnötigen Code vermeiden, indem Sie Abhängigkeiten für verschiedene Typen erstellen.

    Erstellen Sie Abhängigkeiten, die andere Abhängigkeiten als Parameter benötigen


    In den meisten Fällen erstellen wir nicht nur eine Klasse ohne Parameter als Abhängigkeit, sondern erstellen 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 zuvor erstellten Abhängigkeiten zu erstellen, reicht es Kodeinaus, einen instance () -Funktionsaufruf als Parameter zu übergeben. Die Reihenfolge der Schöpfung ist nicht wichtig.

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

    Stattdessen instance()finden Sie im Abschnitt zum Abrufen und Implementieren von Abhängigkeiten möglicherweise Aufrufe provider()oder factory()ausführlichere Informationen zu diesen Methoden.

    Erstellen Sie eine Abhängigkeit, indem Sie die zuvor erstellte Abhängigkeitsmethode aufrufen


    Es hört sich nicht sehr viel an, aber Sie können anrufen instance<TYPE>, um eine Klasse abzurufen, die wir irgendwo bereitstellen, und eine Methode dieser Klasse aufrufen, um eine neue Abhängigkeit abzurufen.

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

    Module


    Mit habe Dagger2ich mich daran gewöhnt, Abhängigkeiten von Modulen zu kürzen. Auf den Kodeinersten Blick sieht alles nicht sehr aus. Sie müssen Applicationviele Abhängigkeiten direkt im Klassenzimmer erstellen, und ich persönlich mag es nicht wirklich. Es gibt jedoch einen Ausweg. Sie Kodeinkönnen damit auch Module erstellen und diese dann bei Bedarf 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 erstellen selbst keine Klassen. Wenn Sie im Modul den Empfang einer Abhängigkeit als Singleton deklarieren und dieses Modul dann in zwei verschiedene Instanzen importieren Kodein, erhalten Sie zwei verschiedene Singletons, eines pro Instanz 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. Daher ist es möglich, das Modul umzubenennen oder seinem Namen ein Präfix hinzuzufügen.

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

    Ich bin es gewohnt zu arbeiten, wenn die Module voneinander abhängen und eine Hierarchie bilden. Sie Kodeinkönnen jedes Modul einmal importieren. Wenn KodeinSie also versuchen, zwei Module mit denselben abhängigen Modulen in ein Objekt zu importieren, stürzt die Anwendung ab. Die Ausgabe ist einfach: Sie müssen einen Aufruf zum Importieren verwenden importOnce(someModule), der prüft, ob ein Modul mit demselben Namen zuvor importiert wurde, und bei Bedarf importiert.

    In solchen Fällen wird die Anwendung beispielsweise fallen:

        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 importOnceSie jedoch erneut versuchen, eine Verbindung herzustellen, 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 zweimal ein Modul verwenden, wird es erstellt auf verschiedenen abhängig, aber was ist das Erbe und implementieren ein ähnliches Verhalten wie Subcomponentszu Dagger2? Es ist ganz einfach: Sie müssen nur von der Instanz erben Kodeinund erhalten im Nachfolger Zugriff auf alle Abhängigkeiten des übergeordneten Elements.

    val kodein: Kodein = Kodein {
                bind<ISchedulersContainer>() with singleton { SchedulersContainer() }
                // другие зависимости
        }
    val subKodein = Kodein {
            extend(kodein)
            // новые зависимости
        }
    

    Überschreiben


    Standardmäßig ist es nicht möglich, die Abhängigkeit zu überschreiben, da Benutzer sonst verrückt werden und nach den Gründen für den fehlerhaften Betrieb der Anwendung suchen. Dies ist jedoch mit Hilfe eines zusätzlichen Parameters der Funktion möglich bind. Diese Funktionalität ist beispielsweise zum Testen nützlich.

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

    Standardmäßig können Module und ihre Abhängigkeiten die bereits im Objekt deklarierten Abhängigkeiten nicht überschreiben. KodeinSie können jedoch beim Importieren eines Moduls angeben, dass dessen Abhängigkeiten vorhandene überschreiben können, und in diesem Modul können Sie Abhängigkeiten angeben, die andere überschreiben können.

    Es klingt nicht ganz klar, lasst uns Beispiele verwenden. 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)
        }
    

    Und in diesem Abhängigkeitsmodul wird die im Objekt deklarierte Abhängigkeit überschrieben 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)
        }
    

    Aber wenn Sie wirklich wollen und verstehen, was Sie tun, können Sie ein solches Modul erstellen, das bei identischen Abhängigkeiten mit dem Objekt Kodeindiese überschreibt und die Anwendung nicht abstürzt. Wir verwenden den Parameter für das Modul allowSilentOverride.

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

    Die Dokumentation befasst sich mit komplexeren Situationen bei der Vererbung und Neudefinition von Abhängigkeiten sowie beim Kopieren von Abhängigkeiten bei den Erben, wobei diese Situationen hier jedoch nicht berücksichtigt werden.

    Abhängigkeiten extrahieren und injizieren


    Schließlich haben wir herausgefunden, wie man Abhängigkeiten auf verschiedene Arten deklariert. Es ist an der Zeit, herauszufinden, wie man sie in unsere Klassen bringt.

    Entwickler haben Kodeinzwei Möglichkeiten, Abhängigkeiten zu ermitteln - injectionund retieval. Kurz gesagt, in diesem injectionFall erhält die Klasse alle Abhängigkeiten während der Erstellung, dh im Konstruktor, und in retrievaldiesem Fall ist die Klasse selbst dafür verantwortlich, ihre Abhängigkeiten abzurufen.

    Wenn Sie injectionIhre Klasse verwenden, wissen Sie nichts darüber Kodeinund der Code in der Klasse ist sauberer. Wenn Sie ihn jedoch verwenden retrieval, haben Sie die Möglichkeit, Abhängigkeiten flexibler zu verwalten. Im Falle retrievalaller Abhängigkeiten werden diese träge erst zum Zeitpunkt des ersten Hinweises auf die Abhängigkeit erhalten.

    Methoden Kodeinzum Abrufen von Abhängigkeiten


    Am Instanz der Klasse Kodeinhat drei Methoden , die Sie zur Sucht, Abhängigkeit Fabrik oder Anbieter Abhängigkeiten zurückkehren werden - ist instance(), factory()und provider()jeweils. Wenn Sie also eine Abhängigkeit mit factoryoder providerangeben, können Sie nicht nur das Ergebnis der Funktion, sondern auch die Funktion selbst erhalten. Vergessen Sie nicht, dass Sie in allen Varianten Tags 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ängigkeiten über den Konstruktor implementieren


    Wie Sie bereits verstanden haben, geht es darum injection. Zur Implementierung müssen Sie zuerst alle Klassenabhängigkeiten in ihrem Konstruktor entfernen und dann eine Instanz der Klasse durch Aufrufen erstellenkodein.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()) }
    }
    

    Einbetten von Abhängigkeiten in nullfähige Eigenschaften


    Möglicherweise wissen Sie nicht, ob eine Abhängigkeit deklariert wurde. Wenn die Abhängigkeit in der Instanz nicht deklariert ist Kodein, führt der Code aus dem obigen Beispiel zu Kodein.NotFoundException. Wenn Sie sich als Ergebnis der Funktion erhalten möchten , nullwenn die Abhängigkeit nicht der Fall, dann gibt es 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()) }
    }
    

    Wir erhalten Abhängigkeiten in einer Klasse


    Wie bereits erwähnt, ist in dem von uns verwendeten Fall retrievaldie Initialisierung aller Abhängigkeiten standardmäßig verzögert. Auf diese Weise können Sie Abhängigkeiten nur zu dem Zeitpunkt abrufen, zu dem sie benötigt werden, und Abhängigkeiten in den Klassen abrufen, die das System erstellt.

    Activity, FragmentUnd andere Klassen mit ihrem eigenen Lebenszyklus, es ist alles über sie.

    Um Abhängigkeiten zu implementieren Activity, benötigen wir nur einen Link zu einer Kopie von Kodein. Danach können wir bereits bekannte Methoden anwenden. In der Tat haben die obigen Beispiele gesehen retrieval, brauchen nur eine Eigenschaft zu deklarieren und eine der Funktionen delegieren: instance(), factory()oderprovider()

    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 das Werk übergeben


    Oben haben Sie bereits gesehen, dass es ausreicht, einen argFunktionsparameter zu verwenden, um einen Parameter an das Werk zu übergeben instance. Aber was tun, wenn es mehrere Parameter gibt (früher habe ich gesagt, dass es in einer Fabrik bis zu 5 Parameter geben kann )? Es muss nur eine argKlasse an den Parameter übergeben werden 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 Standardinitialisierung 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 ausgelöst 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


    Zuvor haben wir ständig explizit eine Instanz erstellt Kodein. Es ist jedoch möglich, die Initialisierung dieser Eigenschaft mithilfe einer Klasse zu verschieben LazyKodein, die eine Funktion im Konstruktor akzeptiert, die ein Objekt zurückgeben soll Kodein.

    Ein solcher Ansatz kann nützlich sein, wenn beispielsweise nicht bekannt ist, ob Abhängigkeiten von dieser 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 и требуемой зависимости
    

    Das Aufrufen 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 für verzögerte Initialisierung


    Für die verzögerte Initialisierung Kodeingibt es ein Objekt LateInitKodein. Sie können dieses Objekt erstellen, die Erstellung von Eigenschaften an dieses delegieren und nach der Initialisierung des Objekts selbst eine Eigenschaft festlegen baseKodein, nach der Sie bereits auf Abhängigkeiten zugreifen können.

    val kodein = LateInitKodein()
    val gson: Gson by kodein.instance()
    /*...*/
    kodein.baseKodein = /* передаем ссылку на экземпляр Kodein */ 
    /*...*/
    gson.fromJson(someStr)
    

    Wir erhalten alle Exemplare des angegebenen Typs


    Sie können Kodein um eine Kopie des angegebenen Typs und aller seiner Erben im Formular bitten 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 ausgeben, sehen Sie dort [32767, 136.88, 4562, 12.46]. Die Abhängigkeit mit dem Tag wird nicht aufgelistet.

    Vereinfachen Sie Abhängigkeiten mithilfe der KodeinAware-Oberfläche


    Diese Schnittstelle erfordert das Überschreiben der type-Eigenschaft Kodeinund bietet im Gegenzug Zugriff auf alle Funktionen, die für die Instanz verfügbar sind 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 by allInstances()stattdessen einfach schreiben . by kodein.allInstances()

    Früher haben wir bereits über einen Auslöser für das Abrufen von Abhängigkeiten gesprochen. In der Schnittstelle können KodeinAwareSie den Trigger überschreiben und alle deklarierten Abhängigkeiten von einem Aufruf dieses Triggers abrufen.

    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 Abhängigkeiten und eine Instanz Kodeinverzögert ist, können Sie die Initialisierung einer Instanz einer Kodein in Kotlin integrierten Funktion delegieren lazy. Ein solcher Ansatz kann in Klassen nützlich sein, die von ihrem Kontext abhängen, z. B. in 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 den 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 Eigenschaften zu delegieren


    Wenn Sie aus irgendeinem Grund die Eigenschaftendelegierung nicht verwenden möchten, können Sie den direkten Zugriff über DKodein (von direkt) verwenden. Der Hauptunterschied besteht darin, dass es keine verzögerte Initialisierung gibt, die Abhängigkeit sofort zum Zeitpunkt des Aufrufs erhalten instancewird provider und ähnliche Funktionen. Sie DKodein können es von Ihrem vorhandenen Kodein oder von Grund auf neu erhalten.

    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 DKodeinAwarekönnen Sie experimentieren.

    Wir bekommen Abhängigkeiten in jedem Kontext


    Um Kodein mehrere Abhängigkeiten desselben Typs von einem Objekt zu erhalten, haben wir bereits die Verwendung von Tags und Factorys mit Argumenten analysiert. Es gibt jedoch auch eine andere Möglichkeit, den Kontext zu verwenden (und dies ist nicht der Kontext in Android).

    Unterschiede zur Abhängigkeit mit dem Tag:

    • Das Tag kann nicht in einer Funktion verwendet werden, in der eine Abhängigkeit erstellt wird.
    • Wenn Sie den Kontext verwenden, haben Sie in der Funktion zum Erstellen von Abhängigkeiten Zugriff auf die Kontextinstanz.

    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 in denselben Typ umwandeln können.

    Sie haben Activityund Presentermöchten beispielsweise mit einem Objekt Kodeinmehrere Abhängigkeiten unterschiedlichen Typs auf unterschiedliche Weise bereitstellen, je nachdem, in welcher Klasse sie sich befinden. 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 praktisch. Deshalb schauen wir uns an, wie man den Kontext benutzt:

    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 ist natürlich weit hergeholt und in der Praxis ist es unwahrscheinlich, dass Sie genau diese Situation haben, aber dieses Beispiel zeigt, wie dieser Kontext funktioniert.

    Um eine Abhängigkeit zu deklarieren, geben Sie nicht an with provider(), sondern with contexted<OurContextClass>().provider, wo OurContextClassist der Typ der Klasse, von der eine Instanz als Kontext fungiert. contextedes kann nur anbieter oder fabrik geben.

    Der Zugriff auf diesen Kontext in einer Funktion, die eine Abhängigkeit zurückgibt, erfolgt über eine Variable unter dem Namen context.

    Um die Abhängigkeit zu ermitteln, die dem Kontext zugeordnet ist, müssen Sie zunächst den Kontext für das Objekt Kodeinüber die Funktion angeben on()und dann nach der Abhängigkeit fragen.

    In ähnlicher Weise wird der Kontext im Fall von verwendet injection.

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

    Erweiterungen für Android


    Zu Beginn des Artikels habe ich versprochen, die Erweiterungsmöglichkeiten für zu prüfen Android.
    Nichts hindert Sie daran, Kodeines wie oben beschrieben zu verwenden, aber Sie können alles um eine Größenordnung bequemer gestalten.

    Eingebautes Kodein-Modul für Android


    Eine sehr nützliche Sache ist ein Modul für Android. Um eine Verbindung herzustellen, muss die Klasse die Eigenschaft träge Applicationimplementieren KodeinAwareund initialisieren Kodein(um auf die Instanz zuzugreifen Application). Stattdessen erhalten Sie eine große Anzahl deklarierter Abhängigkeiten, die von der Klasse abgerufen werden können Application, einschließlich allem, was Sie benötigen Context. So verbinden Sie sich - wir sehen uns 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 Sie zum Beispiel bekommen LayoutInflater. Zum Kennenlernen der vollständigen Liste der im Modul deklarierten Abhängigkeiten - schauen wir hier .

    Wenn Sie diese Abhängigkeiten außerhalb von Android-Klassen erhalten möchten, die Ihren Kontext kennen, geben Sie den Kontext explizit an.

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

    Holen Sie sich schnell übergeordnetes Kodein über "closerKodein ()".


    Es ist einfach, in Android hängen einige Objekte von anderen ab. Auf der obersten Ebene gibt es Anwendung, unter der Aktivität, dann Fragment. Sie können es in der Aktivität implementieren KodeinAwareund als Initialisierung aufrufen closestKodein()und damit eine Instanz Kodeinvon erhalten Application.

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

    closestKodeinSie können auch Nicht-Android-Klassen abrufen, benötigen jedoch einen Android-Kontext, der eine Funktion aufrufen kann. Wenn Sie verwenden KodeinAware- geben Sie auch den Kontext an (definieren Sie die entsprechende Eigenschaft neu 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 einen separaten Kodein in der Aktivität


    Möglicherweise muss das übergeordnete Kodein in der Aktivität übernommen und erweitert werden. Die Ausgabe ist recht einfach.

    class MyActivity : Activity(), KodeinAware {
        private val parentKodein by closestKodein() 
        override val kodein: Kodein by Kodein.lazy { 
            extend(parentKodein) 
            /* новые зависимости */
        }
    }
    

    Kodein, der eine Konfigurationsänderung durchläuft


    Ja, das kannst du und so. Dafür gibt es eine Funktion retainedKodein. Bei Verwendung wird das Objekt Kodeinnach dem Ändern der Konfiguration nicht neu erstellt.

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

    Was wird im Artikel nicht erwähnt?


    Ich gab nicht vor, vollständig zu sein, aber ich selbst verstehe einige Dinge nicht gut genug, um sie auszudrücken. Hier ist eine Liste dessen, was Sie selbst lernen können, wenn Sie die Grundprinzipien kennen:

    • Bereiche
    • Instanzbindung
    • Mehrfachbindung
    • OnReady Rückrufe
    • Externe Quelle
    • Tücken der gelöschten Version
    • Konfigurierbares Kodein
    • JSR-330-Kompatibilität

    Nun, Links zur Dokumentation:


    Vielen Dank, ich hoffe, der Artikel wird Ihnen nützlich sein!