Kodein. O básico

Não encontrei guias compreensíveis para quem Kodein pela primeira vez e a documentação não é transparente e consistente em todos os lugares. Por isso, quero compartilhar os principais recursos da biblioteca com você. Alguns recursos da biblioteca serão lançados, mas essa é basicamente a parte avançada. Aqui você encontrará tudo para iniciar normalmente e começar a implementar dependências com o Kodein enquanto lê o artigo. O artigo é baseado no Kodein 5.3.0 , pois o Kodein 6.0.0 requer a Support Library 28 ou o AndroidX e de maneira alguma todos irão mudar para eles, pois muitas bibliotecas de terceiros ainda não oferecem versões compatíveis.


Kodein é uma biblioteca para implementar injeção de dependência (DI). Se você não conhece esse conceito, leia o início do artigo sobre Dagger2 , onde o autor explica brevemente os aspectos teóricos da DI.

Neste artigo, consideraremos tudo no exemplo do Android, mas, de acordo com os desenvolvedores, o Kodein se comporta da mesma maneira em todas as plataformas suportadas pelo Kotlin (JVM, Android, JS, Nativo).

Instalação


Devido ao fato de Java ter type erasure , surge um problema - o compilador apaga o tipo genérico. No nível do bytecode, List<String> e List<Date> são apenas List . Ainda existe uma maneira de obter informações sobre tipos genéricos, mas custará muito e funcionará apenas na JVM e Android. Nesse sentido, os desenvolvedores do Kodein sugerem o uso de uma das duas dependências: uma recebe informações sobre tipos generalizados ( kodein-generic ) enquanto trabalha e o outro não ( kodein-erased ). Por exemplo, ao usar kodein-erased List<String> kodein-erased por kodein-erased List<String> e a List<Date > serão salvas como List<*> e, ao usar o kodein-generic tudo será salvo junto com o tipo especificado, ou seja, como List<String> e List<Date> respectivamente.

Como escolher?

Escreva não na JVM - use kodein-erased , caso contrário, é impossível.
Escreva na JVM e o problema de desempenho é muito importante para você - você pode usar o kodein-erased , mas tenha cuidado, pois essa experiência pode ser inesperada no mau sentido dessas palavras. Se você estiver criando um aplicativo regular sem nenhum requisito especial de desempenho, use kodein-generic .

Por fim, se você pensar no impacto da DI no desempenho, na maioria das vezes a maioria das dependências é criada uma vez ou as dependências são criadas para reutilização repetida, é improvável que, com essas ações, você possa afetar significativamente o desempenho do seu aplicativo.

Então, instale:

Primeiro - no build.gradle entre os repositórios deve estar o jcenter (), se não estiver lá - adicione.

 buildscript { repositories { jcenter() } } 

Em seguida, no bloco de dependências, adicione uma das dependências básicas mencionadas acima:

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

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

Como estamos falando do Android, haverá mais dependências. Você pode, é claro, ficar sem ele, o Kodein funcionará normalmente, mas por que recusar recursos adicionais úteis para o Android (falarei sobre eles no final do artigo)? A escolha é sua, mas proponho que acrescente.

Também há opções aqui.

Primeiro, você não está usando a SupportLibrary

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

O segundo - use

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

Terceiro - você está usando o AndroidX

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

Começamos a criar dependências


Usando o Dagger2 , estou acostumado a criar e inicializar dependências na inicialização do aplicativo, na classe Application.

Com o Kodein, isso é feito assim:

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

As declarações de dependência sempre começam com

 bind<TYPE>() with 

Tags


A marcação de dependência de Kodein é um recurso semelhante em funcionalidade ao Qualifier do Dagger2 . No Dagger2 você precisa separar o Qualifier ou usar @Named("someTag") , que de fato também é Qualifier . A linha inferior é simples - dessa maneira, você distingue duas dependências do mesmo tipo. Por exemplo, você precisa obter o ontext aplicativo ou uma Activity específica, dependendo da situação; portanto, é necessário especificar tags para isso ao declarar dependências. Kodein permite que Kodein declare uma dependência sem uma tag, ela será a base e, se você não especificar a tag ao receber a dependência, nós a obteremos, as outras precisarão ser marcadas e, quando a dependência for recebida, a tag precisará ser especificada.

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

O parâmetro tag é do tipo Any , para que você possa usar mais do que apenas strings. Mas lembre-se de que as classes usadas como tags devem implementar os métodos equals e hashCode . É sempre necessário passar uma tag para uma função como argumento nomeado, independentemente de você criar ou receber uma dependência.

Tipos de injeção de dependência


Existem várias maneiras de fornecer dependências no Kodein , Kodein pelo essencial - criando singletones. O singleton viverá dentro da estrutura da instância criada do Kodein .

Apresentando singleton


Vamos começar com um exemplo:

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

Assim, fornecemos (forneçamos) IMyDatabase , atrás do qual uma instância do RoomDb ficará oculta. Uma instância do RoomDb será criada na primeira solicitação da dependência e não será Kodein até que uma nova instância do Kodein seja Kodein . Um singleton é criado sincronizado, mas, se desejado, pode ser feito não sincronizado. Isso aumentará a produtividade, mas você deve entender os riscos a seguir.

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

Se você precisar criar uma instância de dependência não na primeira chamada, mas imediatamente após a criação da instância Kodein , use outra função:

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

Criando constantemente uma nova instância da dependência


É possível criar não singletones, mas constantemente ao acessar uma dependência para obter uma nova instância dela. Para isso, a função do provider é usada:

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

Nesse caso, toda vez que solicitarmos uma dependência do IMainPresenter , uma nova instância do QuantityPresenter será criada.

Crie constantemente uma nova instância da dependência e passe os parâmetros para o construtor da dependência


Você pode obter uma nova instância toda vez que adicionar uma dependência, como no exemplo anterior, mas especifique os parâmetros para criar a dependência. Os parâmetros podem ter no máximo 5 . Para esse comportamento, use o método de factory .

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

Sempre que criamos uma instância em cache, dependendo dos parâmetros


Lendo o parágrafo anterior, você pode pensar que seria bom receber não uma nova instância a cada vez, de acordo com os parâmetros passados, mas receber a mesma instância da dependência do mesmo parâmetro.

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

No exemplo acima, quando IntRandom(5, 10) a dependência com os parâmetros 5 e 10 criaremos uma nova instância do IntRandom(5, 10) . Quando chamarmos a dependência novamente com os mesmos parâmetros, obteremos a instância criada anteriormente. Assim, é obtido um map de singleton com inicialização lenta. Os argumentos, como no caso da factory máximo 5 .

Como nas singletones, você pode desativar a sincronização aqui.

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

Usando links suaves e fracos no Kodein


Ao fornecer dependências usando singleton ou multiton você pode especificar o tipo de referência para a instância armazenada. No caso usual, que consideramos acima - este será o elo strong usual. Mas é possível usar links soft e weak . Se você é novo nesses conceitos, dê uma olhada aqui .

Assim, seus singletones podem ser recriados como parte do ciclo de vida do aplicativo ou podem não ser.

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

Singleton separado para cada fluxo


É o mesmo singleton, mas para cada thread que solicita uma dependência, um singleton será criado. Para fazer isso, use o parâmetro familiar ref .

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

Constantes como dependências incorporáveis


Você pode fornecer constantes como dependências. A documentação chama a atenção para o fato de que com o Kodein você deve Kodein constantes de tipos simples sem herança ou interfaces, por exemplo, primitivas ou classes de dados.

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

Crie dependências sem alterar o tipo


Por exemplo, você deseja fornecer a dependência como um singleton, mas não a oculta atrás da interface. Você simplesmente não pode especificar o tipo ao chamar bind e usar from vez de with .

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

A dependência no exemplo acima terá o tipo de retorno da função, ou seja, será Gson uma dependência do tipo Gson .

Criar dependências de subclasse de uma superclasse ou interface


Kodein permite fornecer dependência de maneiras diferentes para os descendentes de uma classe específica ou classes que implementam uma única interface.

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

A classe Animal pode ser uma superclasse ou uma interface. Usando .subtypes , obtemos um animalType tipo TypeToken<*> , do qual já podemos obter uma classe Java e, dependendo dela, fornecer uma dependência de diferentes maneiras. Esse recurso pode ser útil se você usar o TypeToken ou seus derivados como um parâmetro construtor para vários casos. Além disso, você pode evitar código desnecessário com a mesma criação de dependência para diferentes tipos.

Crie dependências que precisam de outras dependências como parâmetros


Na maioria das vezes, não criamos apenas uma classe sem parâmetros como uma dependência, mas criamos uma classe para a qual precisamos passar parâmetros ao construtor.

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

Para criar uma classe com dependências que foram criadas anteriormente no Kodein basta passar uma chamada de função instance () como parâmetros. Nesse caso, a ordem da criação não é importante.

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

Em vez de instance() pode haver chamadas para o provider() ou factory() ; examinaremos mais de perto esses métodos na seção sobre obtenção e implementação de dependências.

Crie uma dependência chamando o método de dependência criado anteriormente


Não parece muito bom, mas você pode chamar a instance<TYPE> para obter uma classe que já fornecemos em algum lugar e chamar o método dessa classe para obter uma nova dependência.

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

Módulos


Usando o Dagger2 , estou acostumado a Dagger2 dependências de Dagger2 . No Kodein , à primeira vista, tudo não parece muito bom. Você precisa criar muitas dependências diretamente na classe Application , e eu pessoalmente não gosto disso. Mas existe uma solução, o Kodein também permite criar módulos e conectá-los nos locais onde for necessário.

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

Mas tenha cuidado, os módulos são apenas contêineres declarando métodos para obter dependências; eles mesmos não criam classes. Portanto, se você declarar o recebimento da dependência como um singleton no módulo e importar esse módulo para duas instâncias diferentes do Kodein , receberá dois singlets diferentes, um por instância do Kodein .

Além disso, o nome de cada módulo deve ser exclusivo. No entanto, se você precisar importar um módulo de outro projeto, é difícil garantir a exclusividade do nome; para isso, é possível renomear o módulo ou adicionar um prefixo ao nome.

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

Estou acostumado a trabalhar quando os módulos dependem um do outro e formam algum tipo de hierarquia. Kodein pode importar cada módulo para o Kodein uma vez; portanto, se você tentar importar dois módulos com os mesmos módulos dependentes em um Kodein , o aplicativo Kodein . A solução é simples - você precisa usar a importOnce(someModule) para importar, que verificará se o módulo com o mesmo nome foi importado anteriormente e depois importará, se necessário.

Por exemplo, nesses casos, o aplicativo falhará:

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

Mas se a chamada importOnce estiver em uma segunda tentativa de conexão, tudo funcionará. Tome 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) } 

Herança


Se você usar o mesmo módulo duas vezes, dependências diferentes serão criadas, mas e a herança e o comportamento de implementação semelhantes aos Subcomponents no Dagger2 ? Tudo é simples, você só precisa herdar da instância Kodein e terá acesso a todas as dependências dos pais no herdeiro.

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

Redefinição


Por padrão, você não pode substituir a dependência, caso contrário, os usuários ficariam loucos procurando motivos para o aplicativo funcionar incorretamente. Mas é possível fazer isso usando um parâmetro adicional da função de bind . Essa funcionalidade será útil, por exemplo, para organizar testes.

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

Por padrão, os módulos e suas dependências não podem substituir dependências já declaradas no objeto Kodein , mas ao importar um módulo, você pode indicar que as dependências existentes podem substituir suas dependências e, dentro deste módulo, você já pode especificar dependências que outras pessoas podem substituir.

Não parece muito claro, vamos usar exemplos. Nesses casos, o aplicativo falhará:

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

E nisso, a dependência do módulo substitui a dependência declarada no 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) } 

Mas se você realmente quiser e entender o que está fazendo, poderá criar um módulo que, se houver dependências idênticas ao objeto Kodein redefinirá e o aplicativo não falhará. Usamos o parâmetro allowSilentOverride para o módulo.

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

A documentação discute situações mais complexas com herança e redefinição de dependências, bem como com dependências de cópia em herdeiros, mas essas situações não serão consideradas aqui.

Recuperando e Injetando Dependências


Finalmente, descobrimos como declarar dependências de várias maneiras; é hora de descobrir como obtê-las em suas classes.

Kodein desenvolvedores do Kodein compartilham duas maneiras de obter dependências - injection e retieval . Em resumo, injection é quando a classe recebe todas as dependências quando é criada, ou seja, no construtor, e retrieval é quando a própria classe é responsável por obter suas dependências.

Ao usar a injection sua classe não sabe nada sobre o Kodein e o código da classe é mais limpo, mas se você usar a retrieval , terá a oportunidade de gerenciar dependências com mais flexibilidade. No caso de retrieval todas as dependências são obtidas preguiçosamente, somente no momento do primeiro apelo à dependência.

Métodos Kodein para usar dependências


Uma instância da classe Kodein possui três métodos que retornam uma dependência, uma fábrica de dependência ou um provedor de dependência - instance() , factory() e provider() respectivamente. Portanto, se você fornecer uma dependência usando uma factory ou provider , poderá receber não apenas o resultado da execução da função, mas também a própria função. Lembre-se de que você pode usar tags em todas as variações.

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

Injeção de dependência através do construtor


Como você já entendeu, será sobre injection . Para implementar, você deve primeiro levar todas as dependências da classe para o construtor e, em seguida, criar uma instância da classe chamando 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()) } } 

Injeção de dependência em propriedades anuláveis


Pode ser que você não saiba se uma dependência foi declarada. Se a dependência não for declarada na instância Kodein , o código do exemplo acima resultará em uma Kodein.NotFoundException . Se você deseja obter null como resultado, se não houver dependência, existem três funções auxiliares para isso: instanceOrNull() , factoryOrNull() e 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()) } } 

Obtenha dependências dentro da classe.


Como já mencionado, no caso em que usamos retrieval , a inicialização de todas as dependências é lenta por padrão. Isso permite que você obtenha dependências somente quando elas são necessárias e obtenha dependências nas classes que o sistema cria.

Activity , Fragment e outras classes com seu próprio ciclo de vida, é tudo sobre eles.

Para implementar dependências no Activity precisamos apenas de um link para uma instância do Kodein, após o qual podemos usar métodos conhecidos. De fato, você já viu exemplos de retrieval acima, basta declarar uma propriedade e delegá-la a uma das funções: instance() , factory() ou provider()

 private val number: BigDecimal by kodein.instance(arg = "23.87") private val numberFactory: (value: String) -> BigDecimal by kodein.factory() private val random: Random? by kodein.instanceOrNull() private val randomProvider: (() -> Random)? by kodein.providerOrNull() 

Passando parâmetros para fábricas


Você já viu que, para passar um parâmetro para a fábrica, basta usar o parâmetro arg da função de instance .Mas e se houver vários parâmetros (eu disse anteriormente que pode haver até 5 parâmetros em uma fábrica )? Você só precisa passar uma argclasse para o parâmetro Mque sobrecarregou os construtores e pode levar 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)) 

Forçar inicialização de dependência


Como eles disseram - por padrão, a inicialização é lenta, mas você pode criar um gatilho, vinculá-lo a uma propriedade, várias propriedades ou uma instância inteira Kodein, depois de puxar esse gatilho e as dependências serão inicializadas.

 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 

Criação de instância preguiçosa do Kodein


Antes disso, constantemente criamos explicitamente uma instância Kodein, mas é possível adiar a inicialização dessa propriedade usando uma classe LazyKodeinque assume uma função no construtor que deve retornar um objeto Kodein.

Essa abordagem pode ser útil se, por exemplo, não se souber se as dependências de uma determinada instância do Kodein são necessárias.

 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    

Uma chamada para Kodein.lazy levará a um resultado semelhante.

  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    

Inicialização atrasada de Kodein


Para inicialização atrasada, Kodeinexiste um objeto LateInitKodein. Você pode criar esse objeto, delegar a criação de propriedades a ele e depois de inicializar o próprio objeto, defina a propriedade para ele baseKodein, após o qual você já poderá acessar as dependências.

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

Obter todas as instâncias do tipo especificado


Você pode solicitar ao Kodein uma instância do tipo especificado e todos os seus descendentes no formulário List. Tudo está apenas dentro da tag especificada. Para fazer isso, existem 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() 

Se você imprimir no log, verá lá [32767, 136.88, 4562, 12.46]. A dependência com a tag não está na lista.

Simplifique a aquisição de dependências usando a interface KodeinAware


Essa interface obriga a substituir a propriedade type Kodeine, em troca, fornece acesso a todas as funções disponíveis para a instância 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 você pode ver, agora você pode simplesmente escrever em by allInstances()vez de by kodein.allInstances()

Anteriormente, já falamos sobre o gatilho para receber dependências. Na interface, KodeinAwarevocê pode substituir um gatilho e obter todas as dependências declaradas quando esse gatilho é chamado.

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

Como o acesso às dependências e à instância Kodeiné lento, você pode delegar a inicialização da instância para a Kodeinfunção interna no Kotlin lazy. Essa abordagem pode ser útil nas classes, dependendo do seu contexto, por exemplo, em Activity.

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

Pelas mesmas razões, você pode usar um 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 } 

Acesso a dependências sem delegar propriedades


Se, por algum motivo, você não quiser usar a delegação de propriedades, poderá usar o acesso direto através DKodein(de direto). A principal diferença é que a inicialização lenta será ido, a dependência será obtido imediatamente no momento da chamada instance, providere funções similares. Você DKodeinpode obtê- lo em uma instância existente do Kodein ou compilar do zero.

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

O Kodein pode ser usado na estrutura KodeinAwaree, DKodeinna estrutura DKodeinAware, você pode experimentar.

Obter dependências em qualquer contexto


Para obter Kodeinvárias dependências do mesmo tipo de um objeto, já examinamos a opção de usar tags e fábricas com argumentos, mas há mais uma coisa: usar um contexto (e esse não é o contexto que está no Android).

Diferenças de dependência com tag:

  • Uma tag não pode ser usada dentro de uma função na qual criamos uma dependência
  • Ao usar o contexto, temos acesso à instância de contexto na função de criação de dependência

Freqüentemente, em vez do contexto, você pode usar uma fábrica com um argumento, e os desenvolvedores Kodeinrecomendam fazer isso se não tiver certeza do que usar. Mas o contexto pode ser útil, por exemplo, quando você não pode converter dois argumentos para o mesmo tipo.

Por exemplo, você tem Activitye Presenter, e deseja, usando um objeto Kodein, fornecer várias dependências de tipos diferentes de maneiras diferentes, dependendo da classe em que são recebidas. Para liderar Activitye Presenterpara um tipo - você precisa de uma interface opcional, ea fábrica terá que verificar o tipo do argumento resultante. O esquema não é muito conveniente. Portanto, examinamos como usar o 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")) } 

Um exemplo, é claro, será puxado sobre as orelhas e, na prática real, é improvável que você encontre exatamente essa situação, mas este exemplo mostra como o contexto funciona.

Para declarar uma dependência, você não especifica with provider(), mas with contexted<OurContextClass>().provider, onde OurContextClassé o tipo de classe, cuja instância atuará como um contexto. contextedsó pode ser fornecedor ou fábrica.

O acesso a esse contexto em uma função que retorna a relação, através do nome de uma variável context.

Para obter uma dependência anexada a um contexto, primeiro é necessário especificar o contexto para o objeto Kodeinpor meio da função on()e solicitar a dependência.

Da mesma forma, o contexto é usado no caso de injection.

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

Extensões Android


No começo do artigo, prometi considerar opções de expansão para Android.
Nada impede você de usá- Kodeinlo como discutimos acima, mas você pode tornar tudo uma ordem de magnitude mais conveniente.

Kodein embutido para Android


Uma coisa muito útil é um módulo preparado para Android. Para conectá-lo, é necessário que a classe Applicationimplemente KodeinAwaree inicialize a propriedade Kodeinlentamente (para acessar a instância Application). Em troca, você obtém um grande número de dependências declaradas que pode obter da classe Application, incluindo tudo o que precisa Context. Como conectar - veja um exemplo.

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

Como você pode ver - você pode obter, por exemplo LayoutInflater. Para uma lista completa das dependências declaradas no módulo - veja aqui .

Se você deseja obter essas dependências fora das classes do Android que conhecem seu contexto, especifique o contexto explicitamente.

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

Obtenha rapidamente o Kodein principal através do closestKodein ()


É simples, no Android, alguns objetos dependem de outros. No nível superior, há Aplicativo, abaixo do qual Atividade, e depois Fragmento. Você pode implementar a atividade KodeinAware, bem como chamada de inicialização closestKodein()e, assim, receber uma cópia Kodeinde Application.

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

closestKodeinVocê também pode obtê-lo fora das classes do Android, mas precisa de um contexto do Android no qual possa chamar a função. Se você o usar KodeinAware, especifique também o contexto (substitua a propriedade correspondente e passe o contexto do Android para a função kcontext()).

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

Criar um Kodein separado na Atividade


Pode ser necessário herdar do Kodein pai na Atividade e expandi-lo. A solução é bastante simples.

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

Kodein que está passando por uma alteração na configuração


Sim você pode. Existe uma função para isso retainedKodein. Ao usá-lo, o objeto Kodeinnão será recriado após uma alteração na configuração.

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

O que não é dito no artigo?


Eu não pretendia ser completo, e eu mesmo não entendo algumas coisas o suficiente para tentar indicá-las. Aqui está uma lista do que você pode aprender por conta própria, conhecendo os princípios básicos:

  • Escopos
  • Ligação de Instância
  • Ligação múltipla
  • Retornos de chamada já
  • Fonte externa
  • Armadilhas da versão apagada
  • Kodein configurável
  • Compatibilidade com JSR-330

Bem e links para a documentação:


Obrigado pela leitura, espero que o artigo seja útil para você!

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


All Articles