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() }
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 arg
classe para o parâmetro M
que 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()
val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() myTrigger.trigger()
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 LazyKodein
que 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()
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()
Inicialização atrasada de Kodein
Para inicialização atrasada, Kodein
existe 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 = 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 Kodein
e, 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, KodeinAware
você 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 Kodein
funçã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
, provider
e funções similares. Você DKodein
pode 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 KodeinAware
e, DKodein
na estrutura DKodeinAware
, você pode experimentar.Obter dependências em qualquer contexto
Para obter Kodein
vá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 Kodein
recomendam 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 Activity
e 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 Activity
e Presenter
para 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. contexted
só 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 Kodein
por 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á- Kodein
lo 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 Application
implemente KodeinAware
e inicialize a propriedade Kodein
lentamente (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))
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 Kodein
de Application
. class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
Você 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 Kodein
nã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ê!