Kodein Los fundamentos

No encontré guías comprensibles para aquellos que Kodein por primera vez, y la documentación no es transparente y consistente en todos los lugares, por lo que quiero compartir las características principales de la biblioteca con ustedes. Se lanzarán algunas características de la biblioteca, pero esta es básicamente la parte avanzada. Aquí encontrará todo para comenzar normalmente y comenzar a implementar dependencias con Kodein medida que lee el artículo. El artículo se basa en Kodein 5.3.0 , ya que Kodein 6.0.0 requiere Support Library 28 o AndroidX y de ninguna manera todos cambiarán a ellos, ya que muchas bibliotecas de terceros aún no ofrecen versiones compatibles.


Kodein es una biblioteca para implementar la inyección de dependencia (DI). Si no está familiarizado con este concepto, lea el comienzo del artículo sobre Dagger2 , donde el autor explica brevemente los aspectos teóricos de la DI.

En este artículo, consideraremos todo con el ejemplo de Android, pero, según los desarrolladores, Kodein se comporta igual en todas las plataformas compatibles con Kotlin (JVM, Android, JS, Native).

Instalación


Debido al hecho de que Java tiene type erasure , surge un problema: el compilador borra el tipo genérico. En el nivel de bytecode, List<String> y List<Date> son solo List . Aún así, queda una forma de obtener información sobre los tipos genéricos, pero costará mucho y solo funcionará en JVM y Android. En este sentido, los desarrolladores de Kodein sugieren usar una de dos dependencias: una recibe información sobre tipos generalizados ( kodein-generic ) mientras trabaja, y la otra no ( kodein-erased ). Por ejemplo, cuando use kodein-erased List<String> y List<Date > se guardarán como List<*> , y cuando use kodein-generic todo se guardará junto con el tipo especificado, es decir, como List<String> y List<Date> respectivamente.

¿Cómo elegir?

No escriba debajo de la JVM: use kodein-erased , de lo contrario es imposible.
Escriba debajo de la JVM y el problema de rendimiento es muy importante para usted: puede usar kodein-erased , pero tenga cuidado, esta experiencia puede ser inesperada en el mal sentido de estas palabras. Si está creando una aplicación regular sin requisitos especiales de rendimiento, use kodein-generic .

En última instancia, si piensa en el impacto de la DI en el rendimiento, la mayoría de las dependencias se crean una vez, o las dependencias se crean para su reutilización repetida, es poco probable que con tales acciones pueda afectar en gran medida el rendimiento de su aplicación.

Entonces, instale:

Primero, en build.gradle entre los repositorios debería estar jcenter (), si no está allí, agregue.

 buildscript { repositories { jcenter() } } 

A continuación, en el bloque de dependencias, agregue una de las dependencias básicas mencionadas anteriormente:

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

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

Como estamos hablando de Android, habrá más dependencias. Por supuesto, puede prescindir de él, Kodein funcionará normalmente, pero ¿por qué rechazar funciones adicionales útiles para Android (hablaré de ellas al final del artículo)? La elección es suya, pero propongo agregar.

También hay opciones aquí.

Primero, no estás usando SupportLibrary

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

El segundo - uso

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

Tercero: estás usando AndroidX

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

Empezamos a crear dependencias.


Con Dagger2 , estoy acostumbrado a crear e inicializar dependencias al inicio de la aplicación, en la clase Aplicación.

Con Kodein, esto se hace así:

 class MyApp : Application() { val kodein = Kodein { /*  */ } } 

Las declaraciones de dependencia siempre comienzan con

 bind<TYPE>() with 

Etiquetas


El etiquetado de dependencia de Kodein es una característica similar en funcionalidad a Qualifier de Dagger2 . En Dagger2 necesitas hacer un Qualifier separado o usar @Named("someTag") , que de hecho también es Qualifier . El resultado final es simple: de esta manera se distinguen dos dependencias del mismo tipo. Por ejemplo, debe obtener el ontext aplicación o una Activity específica según la situación, por lo tanto, debe especificar etiquetas para esto al declarar dependencias. Kodein permite declarar una dependencia sin una etiqueta, será la base y si no especifica la etiqueta al recibir la dependencia, la obtendremos, las otras deben etiquetarse y cuando se reciba la dependencia, se deberá especificar la etiqueta.

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

El parámetro de tag es del tipo Any , por lo que puede usar más que solo cadenas. Pero recuerde que las clases utilizadas como etiquetas deben implementar los métodos equals y hashCode . Siempre es necesario pasar una etiqueta a una función como argumento con nombre, independientemente de si crea una dependencia o la recibe.

Tipos de inyección de dependencia


Hay varias formas de proporcionar dependencias en Kodein , Kodein con lo esencial: crear singletones. El singleton vivirá dentro del marco de la instancia creada de Kodein .

Introduciendo singleton


Comencemos con un ejemplo:

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

Por lo tanto, proporcionamos (proporcionamos) IMyDatabase , detrás del cual se RoomDb una instancia de RoomDb . Se RoomDb una instancia de RoomDb a la primera solicitud de la dependencia; no se volverá a Kodein hasta que se Kodein una nueva instancia de Kodein . Se crea un singleton sincronizado, pero si se desea, se puede hacer sin sincronizar. Esto aumentará la productividad, pero debe comprender los riesgos que siguen.

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

Si necesita crear una instancia de dependencia no en la primera llamada, sino inmediatamente después de crear la instancia de Kodein , use otra función:

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

Crear constantemente una nueva instancia de la dependencia


Es posible crear no singletones, pero constantemente al acceder a una dependencia para obtener una nueva instancia de la misma. Para esto, se utiliza la función de provider :

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

En este caso, cada vez que solicitemos una dependencia IMainPresenter , se IMainPresenter una nueva instancia de QuantityPresenter .

Cree constantemente una nueva instancia de la dependencia y pase los parámetros al constructor de la dependencia


Puede obtener una nueva instancia cada vez que agrega una dependencia, como en el ejemplo anterior, pero especifique los parámetros para crear la dependencia. Los parámetros pueden ser un máximo de 5 . Para este comportamiento, utilice el método de factory .

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

Cada vez que creamos una instancia en caché dependiendo de los parámetros


Al leer el párrafo anterior, podría pensar que sería bueno recibir no una nueva instancia cada vez de acuerdo con los parámetros pasados, sino recibir la misma instancia de la dependencia del mismo parámetro.

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

En el ejemplo anterior, cuando obtenemos la dependencia por primera vez con los parámetros 5 y 10 crearemos una nueva instancia de IntRandom(5, 10) , cuando volvamos a llamar a la dependencia con los mismos parámetros, obtendremos la instancia creada anteriormente. Por lo tanto, se obtiene un map de singleton con inicialización diferida. Los argumentos, como en el caso de la factory máximos 5 .

Al igual que con los singletones, puede desactivar la sincronización aquí.

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

Uso de enlaces débiles y débiles en Kodein


Al proporcionar dependencias mediante singleton o multiton puede especificar el tipo de referencia a la instancia almacenada. En el caso habitual, que consideramos anteriormente, este será el vínculo strong habitual. Pero es posible usar enlaces soft y weak . Si eres nuevo en estos conceptos, echa un vistazo aquí .

Por lo tanto, sus singletones pueden recrearse como parte del ciclo de vida de la aplicación, o pueden no serlo.

 val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } } 

Singleton separado para cada flujo


Este es el mismo singleton, pero para cada hilo que solicite una dependencia, se creará un singleton. Para hacer esto, use el parámetro familiar ref .

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

Constantes como dependencias integrables


Puede proporcionar constantes como dependencias. La documentación llama la atención sobre el hecho de que con Kodein debe Kodein constantes de tipos simples sin herencia o interfaces, por ejemplo, primitivas o clases de datos.

 val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url" 

Crea dependencias sin cambiar el tipo


Por ejemplo, desea proporcionar la dependencia como un singleton, pero no la oculta detrás de la interfaz. Simplemente no puede especificar el tipo al llamar a bind y usar from lugar de with .

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

La dependencia en el ejemplo anterior tendrá el tipo de retorno de la función, es decir, se proporcionará una dependencia de tipo Gson .

Crear dependencias de subclase de una superclase o interfaz


Kodein permite proporcionar dependencia de diferentes maneras para los descendientes de una clase particular o clases que implementan una sola interfaz.

 val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } } 

La clase Animal puede ser una superclase o una interfaz, usando .subtypes obtenemos animalType con el tipo TypeToken<*> , del cual ya podemos obtener la clase Java y, dependiendo de ello, proporcionar la dependencia de diferentes maneras. Esta característica puede ser útil si usa TypeToken o sus derivados como un parámetro de construcción para varios casos. También de esta manera puede evitar código innecesario con la misma creación de dependencias para diferentes tipos.

Crear dependencias que necesiten otras dependencias como parámetros.


Muy a menudo, no solo creamos una clase sin parámetros como una dependencia, sino que creamos una clase a la que necesitamos pasar parámetros al constructor.

 class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway 

Para crear una clase con dependencias que se crearon previamente en Kodein suficiente pasar una llamada a la función instancia () como parámetros. En este caso, el orden de creación no es importante.

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

En lugar de instance() puede haber llamadas al provider() o a la factory() ; veremos más de cerca estos métodos en la sección sobre cómo obtener e implementar dependencias.

Cree una dependencia llamando al método de dependencia creado anteriormente


No suena muy bien, pero puede llamar a la instance<TYPE> para obtener una clase que ya proporcionamos en algún lugar y llamar al método de esta clase para obtener una nueva dependencia.

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

Módulos


Usando Dagger2 , estoy acostumbrado a Dagger2 dependencias del Dagger2 . En Kodein , a primera vista, todo no se ve muy bien. Necesita crear muchas dependencias directamente en la clase Application , y personalmente no me gusta. Pero hay una solución, Kodein también le permite crear módulos y luego conectarlos en esos lugares cuando sea necesario.

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

Pero tenga cuidado, los módulos son solo contenedores que declaran métodos para obtener dependencias; ellos mismos no crean clases. Por lo tanto, si declara la recepción de la dependencia como un singleton en el módulo y luego importa este módulo en dos instancias diferentes de Kodein , obtendrá dos singlets diferentes, uno por instancia de Kodein .

Además, el nombre de cada módulo debe ser único. Sin embargo, si necesita importar un módulo de otro proyecto, es difícil garantizar la unicidad del nombre; para esto, puede cambiar el nombre del módulo o agregar un prefijo a su nombre.

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

Estoy acostumbrado a trabajar cuando los módulos dependen unos de otros y forman algún tipo de jerarquía. Kodein importar cada módulo a Kodein una vez, por lo tanto, si intenta importar dos módulos que tienen los mismos módulos dependientes en un Kodein , la aplicación se bloqueará. La solución es simple: debe usar la importOnce(someModule) para importar, que verificará si el módulo con el mismo nombre se importó previamente y luego importará si es necesario.

Por ejemplo, en tales casos, la aplicación se bloqueará:

  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) } 

Pero si la llamada importOnce está en un segundo intento de conexión, entonces todo funcionará. Ten cuidado

  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) } 

Herencia


Si usa el mismo módulo dos veces, se crearán diferentes dependencias, pero ¿qué pasa con la herencia y el comportamiento de implementación similar a los Subcomponents en Dagger2 ? Todo es simple, solo necesita heredar de la instancia de Kodein y obtendrá acceso a todas las dependencias del padre en el heredero.

 val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() } //   } val subKodein = Kodein { extend(kodein) //   } 

Redefinición


De forma predeterminada, no puede anular la dependencia, de lo contrario los usuarios se volverían locos buscando razones para que la aplicación funcione incorrectamente. Pero es posible hacer esto usando un parámetro adicional de la función de bind . Esta funcionalidad será útil, por ejemplo, para organizar pruebas.

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

De forma predeterminada, los módulos y sus dependencias no pueden anular las dependencias ya declaradas en el objeto Kodein , pero al importar un módulo, puede indicar que las dependencias existentes pueden anular sus dependencias, y dentro de este módulo ya puede especificar dependencias que otros pueden anular.

No suena muy claro, usemos ejemplos. En estos casos, la aplicación se bloqueará:

  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) } 

Y en esto, la dependencia del módulo sobrescribe la dependencia declarada en el objeto 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) } 

Pero si realmente quiere y comprende lo que está haciendo, puede crear un módulo que, si existen dependencias idénticas con el objeto Kodein redefinirá y la aplicación no se bloqueará. Usamos el parámetro allowSilentOverride para el módulo.

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

La documentación analiza situaciones más complejas con la herencia y la redefinición de dependencias, así como con la copia de dependencias en herederos, pero estas situaciones no se considerarán aquí.

Recuperando e inyectando dependencias


Finalmente, descubrimos cómo declarar dependencias de muchas maneras, es hora de descubrir cómo incluirlas en sus clases.

Kodein desarrolladores de Kodein comparten dos formas de obtener dependencias: injection y retieval . En resumen, la injection es cuando la clase recibe todas las dependencias cuando se crea, es decir, en el constructor, y la retrieval es cuando la clase misma es responsable de obtener sus dependencias.

Cuando usa la injection su clase no sabe nada sobre Kodein y el código en la clase es más limpio, pero si usa la retrieval , entonces tiene la oportunidad de administrar las dependencias de manera más flexible. En el caso de la retrieval todas las dependencias se obtienen perezosamente, solo en el momento de la primera apelación a la dependencia.

Métodos de Kodein para usar dependencias


Una instancia de la clase Kodein tiene tres métodos que devuelven una dependencia, una fábrica de dependencias o un proveedor de dependencias: instance() , factory() y provider() respectivamente. Por lo tanto, si proporciona una dependencia utilizando una factory o un provider , puede recibir no solo el resultado de la ejecución de la función, sino también la función misma. Recuerde que puede usar etiquetas en todas las variaciones.

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

Inyección de dependencias a través del constructor


Como ya entendió, se tratará de la injection . Para implementar, primero debe tomar todas las dependencias de la clase en su constructor, y luego crear una instancia de la clase llamando a kodein.newInstance

 class ProductApi(private val client: HttpClient, private val gson: Gson) : IProductApi class Application : Application() { val kodein: Kodein = Kodein { bind<Gson>() with singleton { provideGson() } bind<HttpClient>() with singleton { provideHttpClient() } } private val productApi: IProductApi by kodein.newInstance { ProductApi(instance(), instance()) } } 

Inyección de dependencia en propiedades anulables


Es muy posible que no sepas si se ha declarado una dependencia. Si la dependencia no se declara en la instancia de Kodein , entonces el código del ejemplo anterior dará como resultado una Kodein.NotFoundException . Si desea obtener un null como resultado, si no hay dependencia, existen tres funciones auxiliares para esto: instanceOrNull() , factoryOrNull() y 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()) } } 

Obtenga dependencias dentro de la clase.


Como ya se mencionó, en el caso en que usamos la retrieval , la inicialización de todas las dependencias es lenta por defecto. Esto le permite obtener dependencias solo cuando son necesarias y obtener dependencias en las clases que crea el sistema.

Activity , Fragment y otras clases con su propio ciclo de vida, se trata de ellos.

Para implementar dependencias en Activity solo necesitamos un enlace a una instancia de Kodein, después de lo cual podemos usar métodos conocidos. De hecho, ya ha visto ejemplos de retrieval anteriores, solo necesita declarar una propiedad y delegarla en una de las funciones: instance() , factory() o 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() 

Pasar parámetros a fábricas


Ya ha visto que para pasar un parámetro a la fábrica, es suficiente usar el parámetro arg de la función de instance .Pero, ¿qué pasa si hay varios parámetros (dije anteriormente que puede haber hasta 5 parámetros en una fábrica )? Solo necesita pasar una argclase al parámetro Mque ha sobrecargado a los constructores y puede tomar de 2 a 5 argumentos.

 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)) 

Forzar inicialización de dependencia


Como dijeron: de forma predeterminada, la inicialización es lenta, pero puede crear un desencadenador, vincularlo a una propiedad, varias propiedades o una instancia completa Kodein, después de tirar de este desencadenador y las dependencias se inicializarán.

 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 

Creación de instancia de Lazy Kodein


Antes de eso, constantemente creamos explícitamente una instancia Kodein, pero es posible diferir la inicialización de esta propiedad usando una clase LazyKodeinque toma una función en el constructor que debería devolver un objeto Kodein.

Este enfoque puede ser útil si, por ejemplo, no se sabe si se necesitan dependencias de una instancia de Kodein determinada.

 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    

Una llamada a Kodein.lazy dará lugar a un resultado similar.

  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    

Inicialización retrasada de Kodein


Para la inicialización retrasada, Kodeinexiste un objeto LateInitKodein. Puede crear este objeto, delegarle la creación de propiedades y, después de inicializar el objeto en sí, establecer la propiedad en él baseKodein, después de lo cual ya puede acceder a las dependencias.

 val kodein = LateInitKodein() val gson: Gson by kodein.instance() /*...*/ kodein.baseKodein = /*     Kodein */ /*...*/ gson.fromJson(someStr) 

Obtenga todas las instancias del tipo especificado


Puede pedirle a Kodein una instancia del tipo especificado y todos sus descendientes en el formulario List. Todo está solo dentro de la etiqueta especificada. Para ello, existen métodos allInstances, allProviders, allFactories.

  val kodein: Kodein = Kodein { bind<Number>() with singleton { Short.MAX_VALUE } bind<Double>() with singleton { 12.46 } bind<Double>("someTag") with singleton { 43.89 } bind<Int>() with singleton { 4562 } bind<Float>() with singleton { 136.88f } } val numbers: List<Number> by kodein.allInstances() 

Si imprime en el registro, verá allí [32767, 136.88, 4562, 12.46]. La dependencia con la etiqueta no está en la lista.

Simplifique la adquisición de dependencias utilizando la interfaz KodeinAware


Esta interfaz le obliga a anular la propiedad type Kodeiny, a cambio, proporciona acceso a todas las funciones disponibles para la instancia 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() } 

Como puede ver, ahora puede simplemente escribir en by allInstances()lugar de by kodein.allInstances()

Anteriormente, ya hablamos sobre el desencadenante para recibir dependencias. En la interfaz, KodeinAwarepuede anular un disparador y obtener todas las dependencias declaradas cuando se llama a este disparador.

 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() } } 

Dado que el acceso a las dependencias y la instancia Kodeines diferido, puede delegar la inicialización de la instancia a la Kodeinfunción integrada en Kotlin lazy. Tal enfoque puede ser útil en clases dependiendo de su contexto, por ejemplo, en Activity.

 class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance() 

Por las mismas razones, puede usar un modificador 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 } 

Acceso a dependencias sin delegar propiedades


Si por alguna razón no desea utilizar la delegación de propiedades, puede utilizar el acceso directo a través de DKodein(desde directo). La diferencia principal es que la inicialización perezosa se ha ido, se obtendrá la dependencia de inmediato en el momento de la llamada instance, providery funciones similares. Puede obtenerlo DKodeinde una instancia de Kodein existente o construir desde cero.

 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 se puede usar en el marco KodeinAware, y DKodeinen el marco DKodeinAware, puedes experimentar.

Obtenga dependencias en cualquier contexto


Para obtener Kodeinvarias dependencias del mismo tipo de un objeto, ya hemos examinado la opción de usar etiquetas y fábricas con argumentos, pero hay una cosa más: usar el contexto (y este no es el contexto que está en Android).

Diferencias de dependencia con etiqueta:

  • No se puede usar una etiqueta dentro de una función en la que creamos una dependencia
  • Al usar el contexto, tenemos acceso a la instancia de contexto en la función de creación de dependencia

A menudo, en lugar de contexto, puede usar una fábrica con un argumento, y los desarrolladores Kodeinrecomiendan hacerlo si no está seguro de qué usar. Pero el contexto puede ser útil, por ejemplo, cuando no puede lanzar dos argumentos al mismo tipo.

Por ejemplo, tiene Activityy Presenterdesea usar un objeto Kodeinpara proporcionar varias dependencias de diferentes tipos de diferentes maneras, dependiendo de la clase en la que se reciban. Para dirigir Activityy Presenterpara un tipo - que necesita una interfaz opcional, y la fábrica tendrá que comprobar el tipo del argumento resultante. El esquema no es muy conveniente. Por lo tanto, miramos cómo usar el contexto:

 class MyApplication : Application(), KodeinAware { override val kodein: Kodein = Kodein { bind<BigDecimal>() with contexted<CategoriesActivity>().provider { context.getActivityBigDecimal() } bind<BigDecimal>() with contexted<CategoriesPresenter>().factory { initialValue:BigDecimal -> context.getPresenterBigDecimal(initialValue) } } } class CategoriesActivity : Activity(), AppKodeinAware { fun getActivityBigDecimal() = BigDecimal("16.34") private val activityBigDecimal: BigDecimal by kodein.on(context = this).instance() } class CategoriesPresenter : AppKodeinAware { fun getPresenterBigDecimal(initialValue: BigDecimal) = initialValue * BigDecimal.TEN private val presenterBigDecimal: BigDecimal by kodein.on(context = this).instance(arg = BigDecimal("31.74")) } 

Un ejemplo, por supuesto, pasará por encima de las orejas y en la práctica real es poco probable que se encuentre con tal situación, pero este ejemplo muestra cómo funciona el contexto.

Para declarar una dependencia, especifique no with provider(), pero with contexted<OurContextClass>().provider, donde OurContextClasses el tipo de clase, una instancia de la cual actuará como contexto. contextedsolo puede ser proveedor o fábrica.

El acceso a este contexto en la función que devuelve la dependencia es a través de una variable bajo el nombre context.

Para obtener una dependencia adjunta a un contexto, primero debe especificar el contexto para el objeto a Kodeintravés de la función on()y luego solicitar la dependencia.

Del mismo modo, el contexto se utiliza en el caso de injection.

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

Extensiones de Android


Al comienzo del artículo, prometí considerar opciones de expansión para Android.
Nada le impide usarlo Kodeincomo discutimos anteriormente, pero puede hacer que todo sea un orden de magnitud más conveniente.

Kodein incorporado para Android


Una cosa muy útil es un módulo preparado para Android. Para conectarlo, es necesario que la clase Applicationimplemente KodeinAwaree inicialice la propiedad Kodeinperezosamente (para acceder a la instancia Application). A cambio, obtienes una gran cantidad de dependencias declaradas que puedes obtener de la clase Application, incluido todo lo que necesitas Context. Cómo conectarse: vea un ejemplo.

 class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication)) //  } val inflater: LayoutInflater by instance() } 

Como se puede ver - se puede obtener, por ejemplo LayoutInflater. Para obtener una lista completa de las dependencias declaradas en el módulo, consulte aquí .

Si desea obtener estas dependencias fuera de las clases de Android que conocen su contexto, especifique el contexto explícitamente.

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

Obtenga rápidamente el padre Kodein a través del más cercanoKodein ()


Es simple, en Android, algunos objetos dependen de otros. En el nivel superior hay Aplicación, debajo de la cual está Actividad, luego Fragmento. Puede implementar en Activity KodeinAware, y como inicialización, llamar closestKodein()y obtener una instancia Kodeinde Application.

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

closestKodeinTambién puede obtenerlo fuera de las clases de Android, pero necesita un contexto de Android desde el que pueda llamar a la función. Si lo usa KodeinAware, especifique también el contexto (anule la propiedad correspondiente y pase el contexto de Android a la función kcontext()).

 class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() } 

Crear un Kodein separado en Actividad


Puede ser necesario heredar del padre Kodein en la Actividad y expandirlo. La solución es bastante simple.

 class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) /*   */ } } 

Kodein que está experimentando un cambio de configuración


Si puedes. Hay una función para esto retainedKodein. Al usarlo, el objeto Kodeinno se volverá a crear después de un cambio de configuración.

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

¿Qué no se dice en el artículo?


No pretendí estar completo, y yo mismo no entiendo algunas cosas lo suficientemente bien como para tratar de decirlas. Aquí hay una lista de lo que puede aprender por su cuenta, conociendo los principios básicos:

  • Alcances
  • Enlace de instancia
  • Multi-vinculante
  • Callbacks ya preparados
  • Fuente externa
  • Errores de la versión borrada
  • Kodein configurable
  • Compatibilidad JSR-330

Bien y enlaces a la documentación:


Gracias por leer, ¡espero que el artículo te sea útil!

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


All Articles