Saya tidak menemukan panduan yang dapat dimengerti bagi mereka yang
Kodein
untuk pertama kalinya, dan dokumentasinya tidak transparan dan konsisten di semua tempat, jadi saya ingin berbagi fitur utama perpustakaan dengan Anda. Beberapa fitur perpustakaan akan dirilis, tetapi ini pada dasarnya adalah bagian lanjutan. Di sini Anda akan menemukan segalanya untuk memulai secara normal dan mulai menerapkan dependensi dengan
Kodein
saat Anda membaca artikel. Artikel ini didasarkan pada
Kodein 5.3.0
, karena
Kodein 6.0.0
memerlukan
Support Library 28
atau
AndroidX
dan tidak berarti semua orang akan beralih ke mereka karena banyak perpustakaan pihak ketiga belum menawarkan versi yang kompatibel.

Kodein
adalah pustaka untuk mengimplementasikan injeksi ketergantungan (DI). Jika Anda tidak terbiasa dengan konsep ini, maka bacalah bagian awal
artikel tentang Dagger2 , di mana penulis secara singkat menjelaskan aspek teoritis dari DI.
Pada artikel ini, kami akan mempertimbangkan semuanya dengan contoh Android, tetapi, menurut pengembang, Kodein berlaku sama pada semua platform yang didukung oleh Kotlin (JVM, Android, JS, Native).
Instalasi
Karena fakta bahwa Java memiliki
type erasure
, muncul masalah - kompiler menghapus tipe generik. Pada level bytecode,
List<String>
dan
List<Date>
hanyalah
List
. Meski begitu, masih ada cara untuk mendapatkan informasi tentang tipe generik, tetapi biayanya akan banyak dan hanya berfungsi pada JVM dan Android. Dalam hal ini, pengembang
Kodein
menyarankan untuk menggunakan salah satu dari dua dependensi: satu menerima informasi tentang tipe umum (
kodein-generic
) saat bekerja, dan yang lainnya tidak (
kodein-erased
). Misalnya, ketika menggunakan
List<String>
kodein-erased
List<String>
dan
List<Date
> akan disimpan sebagai
List<*>
, dan ketika menggunakan
kodein-generic
semuanya akan disimpan bersama dengan tipe yang ditentukan, yaitu, seperti
List<String>
dan
List<Date>
masing-masing.
Bagaimana memilih?
Tulis
tidak di bawah JVM - gunakan
kodein-erased
, kalau tidak, tidak mungkin.
Tulis di bawah JVM dan masalah kinerja sangat penting bagi Anda - Anda dapat menggunakan
kodein-erased
, tetapi hati-hati, pengalaman ini mungkin tidak terduga dalam arti buruk dari kata-kata ini. Jika Anda membuat aplikasi reguler tanpa persyaratan kinerja khusus, gunakan
kodein-generic
.
Pada akhirnya, jika Anda memikirkan dampak DI terhadap kinerja, maka paling sering sebagian besar dependensi dibuat satu kali, atau dependensi dibuat untuk digunakan kembali berulang kali, kecil kemungkinannya dengan tindakan seperti itu Anda dapat sangat mempengaruhi kinerja aplikasi Anda.
Jadi, instal:
Pertama - di build.gradle di antara repositori harus jcenter (), jika tidak ada - tambahkan.
buildscript { repositories { jcenter() } }
Selanjutnya, di blok dependensi, tambahkan salah satu dependensi dasar yang disebutkan di atas:
implementation "org.kodein.di:kodein-di-generic-jvm:$version"
implementation "org.kodein.di:kodein-di-erased-jvm:$version"
Karena kita berbicara tentang Android, akan ada lebih banyak ketergantungan. Anda tentu saja dapat melakukannya tanpa itu, Kodein akan berfungsi secara normal, tetapi mengapa menolak fitur tambahan yang berguna untuk Android (saya akan membicarakannya di akhir artikel)? Pilihan ada di tangan Anda, tetapi saya ingin menambahkan.
Ada juga opsi di sini.
Pertama, Anda tidak menggunakan
SupportLibrary
implementation "org.kodein.di:kodein-di-framework-android-core:$version"
Yang kedua - gunakan
implementation "org.kodein.di:kodein-di-framework-android-support:$version"
Ketiga - Anda menggunakan AndroidX
implementation "org.kodein.di:kodein-di-framework-android-x:$version"
Kami mulai membuat dependensi
Menggunakan
Dagger2
, saya terbiasa membuat dan menginisialisasi dependensi saat startup aplikasi, di kelas Aplikasi.
Dengan Kodein, ini dilakukan seperti ini:
class MyApp : Application() { val kodein = Kodein { } }
Deklarasi ketergantungan selalu dimulai dengan
bind<TYPE>() with
Tag
Penandaan ketergantungan kodein adalah fitur yang serupa dalam fungsionalitas dengan
Qualifier
dari
Dagger2
. Di
Dagger2
Anda harus melakukan
Qualifier
terpisah atau menggunakan
@Named("someTag")
, yang sebenarnya juga merupakan
Qualifier
. Intinya sederhana - dengan cara ini Anda membedakan dua dependensi dari tipe yang sama. Misalnya, Anda perlu mendapatkan
ontext
aplikasi atau
Activity
tertentu tergantung pada situasinya, oleh karena itu Anda perlu menentukan tag untuk ini ketika mendeklarasikan dependensi.
Kodein
memungkinkan
Kodein
untuk mendeklarasikan satu dependensi tanpa tag, itu akan menjadi base satu dan jika Anda tidak menentukan tag ketika menerima dependensi, kami akan mendapatkannya, yang lain harus ditandai dan ketika dependensi diterima, tag perlu ditentukan.
val kodein = Kodein { bind<Context>() with ... bind<Context>(tag = "main_activity") with ... bind<Context>(tag = "sale_activity") with ... }
Parameter
tag
bertipe
Any
, jadi Anda bisa menggunakan lebih dari sekadar string. Tapi ingat bahwa kelas yang digunakan sebagai tag harus mengimplementasikan metode
equals
dan
hashCode
. Selalu diperlukan untuk meneruskan tag ke fungsi sebagai argumen bernama, terlepas dari apakah Anda membuat ketergantungan atau menerimanya.
Jenis Injeksi Ketergantungan
Ada beberapa cara untuk menyediakan dependensi dalam
Kodein
,
Kodein
dengan hal-hal penting - membuat singletones. Singleton akan hidup dalam kerangka instancein
Kodein
dibuat.
Memperkenalkan singleton
Mari kita mulai dengan sebuah contoh:
val kodein = Kodein { bind<IMyDatabase>() with singleton { RoomDb() } }
Dengan demikian kami menyediakan (menyediakan)
IMyDatabase
, di belakangnya sebuah instance dari
RoomDb
akan disembunyikan. Sebuah instance dari
RoomDb
akan dibuat atas permintaan dependensi pertama, itu tidak akan
Kodein
sampai instance
Kodein
baru
Kodein
. Singleton dibuat tersinkronisasi, tetapi jika diinginkan, ia dapat dibuat tidak disinkronkan. Ini akan meningkatkan produktivitas, tetapi Anda harus memahami risiko yang mengikutinya.
val kodein = Kodein { bind<IMyDatabase>() with singleton(sync = false) { RoomDb() } }
Jika Anda perlu membuat instance dependensi bukan pada panggilan pertama, tetapi segera setelah membuat instance
Kodein
, gunakan fungsi lain:
val kodein = Kodein { bind<IMyDatabase>() with eagerSingleton { RoomDb() } }
Secara terus-menerus menciptakan contoh baru ketergantungan
Dimungkinkan untuk membuat bukan singletones, tetapi secara konstan ketika mengakses dependensi untuk mendapatkan instance baru darinya. Untuk ini, fungsi
provider
digunakan:
val kodein = Kodein { bind<IMainPresenter>() with provider { QuantityPresenter() } }
Dalam hal ini, setiap kali kami meminta ketergantungan
IMainPresenter
, instance baru dari
QuantityPresenter
akan dibuat.
Secara konstan membuat instance baru dependensi dan meneruskan parameter ke konstruktor dependensi
Anda bisa mendapatkan instance baru setiap kali Anda menambahkan dependensi, seperti pada contoh sebelumnya, tetapi tentukan parameter untuk membuat dependensi. Parameter bisa maksimal
5 . Untuk perilaku ini, gunakan metode
factory
.
val kodein = Kodein { bind<IColorPicker>() with factory { r: Int, g: Int, b: Int, a: Int -> RgbColorPicker(r, g, b, a) } }
Setiap kali kami membuat contoh cache tergantung pada parameter
Membaca paragraf sebelumnya, Anda mungkin berpikir bahwa akan lebih baik untuk menerima bukan instance baru setiap kali sesuai dengan parameter yang diteruskan, tetapi untuk menerima instance ketergantungan yang sama pada parameter yang sama.
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton { from: Int, to: Int -> IntRandom(from, to) } }
Pada contoh di atas, ketika kita pertama kali mendapatkan dependensi dengan parameter
5
dan
10
kita akan membuat instance baru dari
IntRandom(5, 10)
, ketika kita memanggil dependensi lagi dengan parameter yang sama, kita akan mendapatkan instance yang dibuat sebelumnya. Dengan demikian,
map
dari singleton dengan inisialisasi malas diperoleh. Argumen, seperti dalam kasus
factory
maksimum
5 .
Seperti halnya singletones, Anda dapat menonaktifkan sinkronisasi di sini.
val kodein = Kodein { bind<IRandomIntGenerator>() with multiton(sync = false) { from: Int, to: Int -> IntRandom(from, to) } }
Menggunakan Tautan Lunak dan Lemah pada Kodein
Saat memberikan dependensi menggunakan
singleton
atau
multiton
Anda dapat menentukan jenis referensi ke instance tersimpan. Dalam kasus biasa, yang kami pertimbangkan di atas - ini akan menjadi tautan
strong
biasa. Namun dimungkinkan untuk menggunakan tautan
soft
dan
weak
. Jika Anda baru mengenal konsep-konsep ini, silakan
lihat di sini .
Dengan demikian, lajang Anda dapat diciptakan kembali sebagai bagian dari siklus hidup aplikasi, atau mungkin juga tidak.
val kodein = Kodein { bind<IMyMap>() with singleton(ref = softReference) { WorldMap() } bind<IClient>() with singleton(ref = weakReference) { id -> clientFromDB(id) } }
Pisahkan singleton untuk setiap aliran
Ini adalah singleton yang sama, tetapi untuk setiap utas yang meminta ketergantungan, singleton akan dibuat. Untuk melakukan ini, gunakan
ref
parameter yang dikenal.
val kodein = Kodein { bind<Cache>() with singleton(ref = threadLocal) { LRUCache(16 * 1024) } }
Konstanta sebagai Ketergantungan yang Dapat Disematkan
Anda dapat memberikan konstanta sebagai dependensi. Dokumentasi menarik perhatian pada fakta bahwa dengan
Kodein
Anda harus
Kodein
konstanta tipe sederhana tanpa pewarisan atau antarmuka, misalnya kelas primitif atau data.
val kodein = Kodein { constant(tag = "maxThread") with 8 constant(tag = "serverURL") with "https://my.server.url"
Buat dependensi tanpa mengubah jenisnya
Misalnya, Anda ingin memberikan dependensi sebagai singleton, tetapi jangan menyembunyikannya di belakang antarmuka. Anda tidak bisa menentukan jenisnya saat memanggil
bind
dan menggunakan
from
alih-alih
with
.
val kodein = Kodein { bind() from singleton { Gson() }
Ketergantungan pada contoh di atas akan memiliki tipe kembalinya fungsi, yaitu, ketergantungan tipe
Gson
akan
Gson
.
Buat dependensi subclass dari superclass atau antarmuka
Kodein
memungkinkan
Kodein
untuk memberikan ketergantungan dengan cara yang berbeda untuk keturunan dari kelas atau kelas tertentu yang mengimplementasikan antarmuka tunggal.
val kodein = Kodein { bind<Animal>().subTypes() with { animalType -> when (animalType.jvmType) { Dog::class.java -> eagerSingleton { Dog() } else -> provider { WildAnimal(animalType) } } }
Kelas
Animal
dapat berupa superclass atau antarmuka, dengan menggunakan
.subtypes
kita mendapatkan
animalType
tipe
TypeToken<*>
, dari mana kita bisa mendapatkan kelas Java dan, tergantung padanya, memberikan ketergantungan dengan berbagai cara. Fitur ini dapat berguna jika Anda menggunakan
TypeToken
atau turunannya sebagai parameter konstruktor untuk sejumlah kasus. Juga dengan cara ini Anda dapat menghindari kode yang tidak perlu dengan pembuatan dependensi yang sama untuk berbagai jenis.
Buat dependensi yang membutuhkan dependensi lain sebagai parameter
Paling sering, kita tidak hanya membuat kelas tanpa parameter sebagai dependensi, tetapi membuat kelas yang kita perlukan untuk meneruskan parameter ke konstruktor.
class ProductGateway(private val api: IProductApi, private val dispatchers: IDispatchersContainer) : IProductGateway
Untuk membuat kelas dengan dependensi yang sebelumnya dibuat dalam
Kodein
cukup untuk melewati fungsi panggilan instance () sebagai parameter. Dalam hal ini, urutan penciptaan tidak penting.
bind<IDispatchersContainer>() with singleton { DispatchersContainer() } bind<IProductGateway>() with singleton { ProductGateway(instance(), instance()) } bind<IProductApi>() with singleton { ProductApi() }
Alih-alih
instance()
mungkin ada panggilan ke
provider()
atau
factory()
; kami akan melihat lebih dekat pada metode ini di bagian tentang memperoleh dan mengimplementasikan dependensi.
Buat ketergantungan dengan memanggil metode ketergantungan yang dibuat sebelumnya
Kedengarannya tidak terlalu bagus, tetapi Anda dapat memanggil
instance<TYPE>
untuk mendapatkan kelas yang sudah kami sediakan di suatu tempat dan memanggil metode kelas ini untuk mendapatkan ketergantungan baru.
bind<DataSource>() with singleton { MySQLDataSource() } bind<Connection>() with provider { instance<DataSource>().openConnection() }
Modul
Menggunakan
Dagger2
, saya terbiasa
Dagger2
dependensi
Dagger2
. Dalam
Kodein
, pada pandangan pertama, semuanya tidak terlihat sangat bagus. Anda perlu membuat banyak dependensi tepat di kelas
Application
, dan saya pribadi tidak begitu menyukainya. Tapi ada solusinya,
Kodein
juga memungkinkan Anda membuat modul, dan kemudian menghubungkannya di tempat-tempat yang diperlukan.
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() }
Tapi hati-hati, modul hanyalah wadah yang menyatakan metode untuk mendapatkan dependensi, mereka sendiri tidak membuat kelas. Oleh karena itu, jika Anda mendeklarasikan penerimaan ketergantungan sebagai singleton dalam modul, dan kemudian mengimpor modul ini ke dalam dua contoh berbeda dari
Kodein
, maka Anda akan mendapatkan dua singlet yang berbeda, satu per instance dari
Kodein
.
Juga, nama setiap modul harus unik. Namun, jika Anda perlu mengimpor modul dari proyek lain, sulit untuk menjamin keunikan nama, karena ini, Anda dapat mengganti nama modul atau menambahkan awalan ke namanya.
import(apiModule.copy(name = "firstAPI")) import(secondApiModule.copy(prefix = "secondAPI-"))
Saya terbiasa bekerja ketika modul-modul saling bergantung satu sama lain dan membentuk semacam hierarki.
Kodein
dapat mengimpor setiap modul ke dalam
Kodein
sekali, oleh karena itu, jika Anda mencoba mengimpor dua modul yang memiliki modul dependen yang sama ke dalam satu
Kodein
, aplikasi akan macet. Solusinya sederhana - Anda perlu menggunakan
importOnce(someModule)
untuk mengimpor, yang akan memeriksa apakah modul dengan nama yang sama sebelumnya diimpor, dan kemudian impor jika perlu.
Misalnya, dalam kasus seperti itu, aplikasi akan macet:
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) }
Tetapi jika panggilan
importOnce
melakukan upaya koneksi kedua, maka semuanya akan berfungsi. Berhati-hatilah.
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) }
Warisan
Jika Anda menggunakan modul yang sama dua kali, dependensi yang berbeda akan dibuat, tetapi bagaimana dengan pewarisan dan perilaku implementasi yang mirip dengan
Subcomponents
di
Dagger2
? Semuanya sederhana, Anda hanya perlu mewarisi dari instance
Kodein
dan Anda akan mendapatkan akses ke semua dependensi induk dalam pewarisnya.
val kodein: Kodein = Kodein { bind<ISchedulersContainer>() with singleton { SchedulersContainer() }
Redefinisi
Secara default, Anda tidak bisa mengesampingkan dependensi, jika tidak, pengguna akan menjadi gila mencari alasan agar aplikasi tidak berfungsi dengan benar. Tetapi dimungkinkan untuk melakukan ini menggunakan parameter tambahan dari fungsi
bind
. Fungsi ini akan bermanfaat, misalnya, untuk mengatur pengujian.
val kodein = Kodein { bind<Api>() with singleton { ApiImpl() } bind<Api>(overrides = true) with singleton { OtherApiImpl() } }
Secara default, modul dan dependensinya tidak dapat mengesampingkan dependensi yang sudah dideklarasikan dalam objek
Kodein
, tetapi ketika mengimpor modul, Anda dapat menentukan bahwa dependensinya dapat ditimpa oleh yang sudah ada, dan di dalam modul ini Anda sudah dapat menentukan dependensi yang dapat ditimpa oleh orang lain.
Kedengarannya tidak terlalu jelas, mari kita gunakan contoh. Dalam kasus ini, aplikasi akan macet:
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) }
Dan dalam hal ini, dependensi modul menimpa dependensi yang dideklarasikan dalam objek
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) }
Tetapi jika Anda benar-benar ingin dan Anda mengerti apa yang Anda lakukan, maka Anda dapat membuat modul itu, jika ada dependensi identik dengan objek
Kodein
akan mendefinisikannya kembali dan aplikasi tidak akan crash. Kami menggunakan parameter
allowSilentOverride
untuk modul.
val testModule = Kodein.Module(name = "test", allowSilentOverride = true) { bind<EmailClient>() with singleton { MockEmailClient() } }
Dokumentasi membahas situasi yang lebih kompleks dengan pewarisan dan pendefinisian ulang dependensi, serta dengan menyalin ketergantungan pada ahli waris, tetapi situasi ini tidak akan dipertimbangkan di sini.
Mengambil dan menyuntikkan dependensi
Akhirnya, kami menemukan cara mendeklarasikan dependensi dalam banyak cara, saatnya mencari cara untuk mendapatkannya di kelas mereka.
Pengembang
Kodein
berbagi dua cara untuk mendapatkan dependensi -
injection
dan
retieval
. Singkatnya,
injection
adalah ketika kelas menerima semua dependensi ketika dibuat, yaitu, di konstruktor, dan
retrieval
adalah ketika kelas itu sendiri bertanggung jawab untuk mendapatkan dependensinya.
Saat menggunakan
injection
kelas Anda tidak tahu apa-apa tentang
Kodein
dan kode di kelas lebih bersih, tetapi jika Anda menggunakan
retrieval
, maka Anda memiliki kesempatan untuk mengelola dependensi secara lebih fleksibel. Dalam hal
retrieval
semua dependensi diperoleh dengan malas, hanya pada saat banding pertama terhadap dependensi.
Metode Kodein
untuk Menggunakan Dependensi
Sebuah instance dari kelas
Kodein
memiliki tiga metode yang mengembalikan dependensi, pabrik dependensi, atau penyedia dependensi -
instance()
,
factory()
dan
provider()
masing-masing. Jadi, jika Anda memberikan ketergantungan menggunakan
factory
atau
provider
, maka Anda tidak hanya dapat menerima hasil dari eksekusi fungsi, tetapi juga fungsi itu sendiri. Ingatlah bahwa Anda dapat menggunakan tag di semua variasi.
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()
Ketergantungan injeksi melalui konstruktor
Seperti yang sudah Anda pahami, ini tentang
injection
. Untuk mengimplementasikan, Anda harus terlebih dahulu mengambil semua dependensi kelas ke dalam konstruktornya, dan kemudian membuat instance kelas dengan memanggil
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()) } }
Ketergantungan injeksi dalam sifat yang dapat dibatalkan
Mungkin Anda tidak tahu apakah dependensi telah diumumkan. Jika dependensi tidak dideklarasikan dalam instance
Kodein
, maka kode dari contoh di atas akan menghasilkan
Kodein.NotFoundException
. Jika Anda ingin mendapatkan
null
sebagai hasilnya, jika tidak ada ketergantungan, maka ada tiga fungsi tambahan untuk ini:
instanceOrNull()
,
factoryOrNull()
dan
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()) } }
Dapatkan dependensi di dalam kelas.
Seperti yang telah disebutkan, dalam kasus ketika kita menggunakan
retrieval
, inisialisasi semua dependensi secara default malas. Ini memungkinkan Anda untuk mendapatkan dependensi hanya saat dibutuhkan, dan mendapatkan dependensi di kelas yang dibuat oleh sistem.
Activity
,
Fragment
dan kelas-kelas lain dengan siklus hidup mereka sendiri, semua tentang mereka.
Untuk menerapkan dependensi dalam
Activity
kita hanya perlu tautan ke instance Kodein, setelah itu kita dapat menggunakan metode yang terkenal. Bahkan, Anda telah melihat contoh
retrieval
atas, Anda hanya perlu mendeklarasikan properti dan mendelegasikannya ke salah satu fungsi:
instance()
,
factory()
atau
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()
Melewati parameter ke pabrik
Anda telah melihat bahwa untuk mengirimkan parameter ke pabrik, cukup menggunakan parameter
arg
dari fungsi
instance
.
Tetapi bagaimana jika ada beberapa parameter (saya katakan sebelumnya bahwa bisa ada hingga 5 parameter di pabrik )? Anda hanya perlu meneruskan arg
kelas ke parameter M
yang memiliki konstruktor kelebihan beban dan dapat mengambil dari 2 hingga 5 argumen. 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))
Inisialisasi dependensi ketergantungan
Seperti yang mereka katakan - secara default, inisialisasi malas, tetapi Anda dapat membuat pemicu, mengikatnya ke properti, beberapa properti atau seluruh instance Kodein
, setelah menarik pemicu ini dan dependensi akan diinisialisasi. 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()
Lazy Kodein misalnya pembuatan
Sebelum itu, kami secara konstan membuat instance Kodein
, tetapi dimungkinkan untuk menunda inisialisasi properti ini menggunakan kelas LazyKodein
yang mengambil fungsi dalam konstruktor yang harus mengembalikan objek Kodein
.Pendekatan ini dapat berguna jika, misalnya, tidak diketahui apakah dependensi dari instance Kodein tertentu diperlukan. 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()
Panggilan ke Kodein.lazy akan menghasilkan hasil yang serupa. 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()
Inisialisasi Kodein Tertunda
Untuk inisialisasi tertunda, Kodein
ada objek LateInitKodein
. Anda bisa membuat objek ini, mendelegasikan pembuatan properti padanya, dan setelah Anda menginisialisasi objek itu sendiri, atur properti itu baseKodein
, setelah itu Anda sudah bisa mengakses dependensi. val kodein = LateInitKodein() val gson: Gson by kodein.instance() kodein.baseKodein = gson.fromJson(someStr)
Dapatkan semua contoh dari tipe yang ditentukan
Anda dapat meminta Kodein untuk instance dari tipe yang ditentukan dan semua turunannya dalam formulir List
. Semuanya hanya dalam tag yang ditentukan. Untuk melakukan hal ini, ada metode 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()
Jika Anda mencetak ke log, Anda akan melihat di sana [32767, 136,88, 4562, 12,46]. Ketergantungan dengan tag tidak ada dalam daftar.Sederhanakan akuisisi ketergantungan menggunakan antarmuka KodeinAware
Antarmuka ini mewajibkan Anda untuk mengganti properti tipe Kodein
, dan sebagai gantinya memberikan akses ke semua fungsi yang tersedia untuk instance 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() }
Seperti yang Anda lihat, sekarang Anda bisa menulis by allInstances()
alih-alih by kodein.allInstances()
Sebelumnya, kami sudah berbicara tentang pemicu untuk menerima dependensi. Di antarmuka, KodeinAware
Anda bisa mengganti pemicu dan mendapatkan semua dependensi yang dideklarasikan saat pemicu ini dipanggil. 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() } }
Karena akses ke dependensi dan instance Kodein
malas, Anda dapat mendelegasikan inisialisasi instance Kodein
ke fungsi yang dibangun di Kotlin lazy
. Pendekatan semacam itu mungkin berguna di kelas tergantung pada konteksnya, misalnya di Activity
. class CategoriesActivity : Activity(), KodeinAware { override val kodein: Kodein by lazy { (application as MyApplication).kodein } private val myFloat: Float by instance()
Untuk alasan yang sama, Anda dapat menggunakan pengubah 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 }
Akses ke dependensi tanpa mendelegasikan properti
Jika karena alasan tertentu Anda tidak ingin menggunakan delegasi properti, maka Anda dapat menggunakan akses langsung melalui DKodein
(dari langsung). Perbedaan utama adalah bahwa tidak akan ada inisialisasi malas lagi, ketergantungan akan diperoleh segera pada saat panggilan instance
, provider
dan fungsi serupa. Anda DKodein
bisa mendapatkannya dari instance Kodein yang ada atau membangun dari awal. 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 dapat digunakan dalam framework KodeinAware
, dan DKodein
dalam framework DKodeinAware
, Anda bisa bereksperimen.Dapatkan dependensi dalam konteks apa pun
Untuk mendapatkan Kodein
beberapa dependensi dengan tipe yang sama dari satu objek, kami telah memeriksa opsi untuk menggunakan tag dan pabrik dengan argumen, tetapi ada satu hal lagi - menggunakan konteks (dan ini bukan konteks yang ada di Android).Perbedaan dari ketergantungan dengan tag:- Tag tidak dapat digunakan di dalam fungsi tempat kami membuat dependensi
- Saat menggunakan konteks, kami memiliki akses ke instance konteks dalam fungsi pembuatan dependensi
Seringkali, alih-alih konteks, Anda dapat menggunakan pabrik dengan argumen, dan pengembang Kodein
menyarankan melakukan ini jika Anda tidak yakin apa yang harus digunakan. Tapi konteksnya bisa bermanfaat, misalnya, ketika Anda tidak bisa melemparkan dua argumen ke tipe yang sama.Misalnya, Anda memiliki Activity
dan Presenter
, dan Anda ingin, menggunakan satu objek Kodein
, untuk menyediakan beberapa dependensi dari berbagai jenis dengan cara yang berbeda, tergantung pada kelas apa mereka diterima. Untuk memimpin Activity
dan Presenter
untuk satu jenis - Anda perlu sebuah antarmuka opsional, dan pabrik harus memeriksa jenis argumen yang dihasilkan. Skema ini sangat tidak nyaman. Oleh karena itu, kami melihat cara menggunakan konteks: 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")) }
Contoh, tentu saja, akan ditarik ke telinga dan dalam praktik nyata tidak mungkin Anda akan menghadapi situasi seperti itu, tetapi contoh ini menunjukkan bagaimana konteksnya bekerja.Untuk mendeklarasikan dependensi, Anda tidak menentukan with provider()
, tetapi with contexted<OurContextClass>().provider
, di mana OurContextClass
adalah tipe kelas, sebuah instance yang akan bertindak sebagai konteks. contexted
hanya dapat penyedia atau pabrik.Akses ke konteks ini dalam fungsi yang mengembalikan ketergantungan dilakukan melalui variabel bernama context
.Untuk mendapatkan dependensi yang dilampirkan ke konteks, Anda harus terlebih dahulu menentukan konteks untuk objek Kodein
melalui fungsi on()
, dan kemudian meminta dependensi.Demikian pula, konteks digunakan dalam kasus injection
. private val productApi: IProductApi by kodein.on(context = someContext).newInstance { ProductApi(instance(), instance()) } }
Ekstensi Android
Di awal artikel, saya berjanji untuk mempertimbangkan opsi ekspansi Android
.Tidak ada yang menghalangi Anda untuk menggunakannya Kodein
seperti yang telah kita bahas di atas, tetapi Anda dapat membuat semuanya menjadi lebih mudah.Kodein bawaan untuk Android
Suatu hal yang sangat berguna adalah modul yang disiapkan untuk Android. Untuk menghubungkannya, kelas perlu Application
mengimplementasikan KodeinAware
dan menginisialisasi properti dengan Kodein
malas (untuk mengakses instance Application
). Sebagai gantinya, Anda mendapatkan sejumlah besar dependensi yang dideklarasikan yang bisa Anda dapatkan dari kelas Application
, termasuk semua yang Anda butuhkan Context
. Cara menghubungkan - lihat contoh. class MyApplication : Application(), KodeinAware { override val kodein = Kodein.lazy { import(androidModule(this@MyApplication))
Seperti yang Anda lihat - Anda bisa mendapatkan, misalnya LayoutInflater
. Untuk daftar lengkap dependensi yang dideklarasikan dalam modul - lihat di sini .Jika Anda ingin mendapatkan dependensi ini di luar kelas Android yang mengetahui konteksnya, tentukan konteksnya secara eksplisit. val inflater: LayoutInflater by kodein.on(context = getActivity()).instance()
Dapatkan kode induk dengan cepat melalui terdekatKodein ()
Sederhana saja, di Android, beberapa objek bergantung pada yang lain. Di tingkat atas ada Aplikasi, yang di bawahnya adalah Aktivitas, lalu Fragmen. Anda dapat menerapkan dalam Aktivitas KodeinAware
, dan sebagai inisialisasi, panggil closestKodein()
dan dapatkan instance Kodein
dari Application
. class MyActivity : Activity(), KodeinAware { override val kodein by closestKodein() val ds: DataSource by instance() }
closestKodein
Anda juga bisa mendapatkannya di luar kelas Android, tetapi Anda membutuhkan konteks Android tempat Anda dapat memanggil fungsinya. Jika Anda menggunakannya KodeinAware
, tentukan juga konteksnya (ganti properti yang sesuai dan berikan konteks Android ke fungsi kcontext()
). class MyController(androidContext: Context) : KodeinAware { override val kodein by androidContext.closestKodein() override val kodeinContext = kcontext(androidContext) val inflater: LayoutInflater by instance() }
Buat Kodein terpisah di Activity
Mungkin perlu untuk mewarisi dari induk Kodein dalam Kegiatan dan mengembangkannya. Solusinya cukup sederhana. class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by Kodein.lazy { extend(parentKodein) } }
Kodein yang sedang mengalami perubahan konfigurasi
Ya kamu bisa. Ada fungsi untuk ini retainedKodein
. Saat menggunakannya, objek Kodein
tidak akan dibuat ulang setelah perubahan konfigurasi. class MyActivity : Activity(), KodeinAware { private val parentKodein by closestKodein() override val kodein: Kodein by retainedKodein { extend(parentKodein) } }
Apa yang tidak dikatakan dalam artikel itu?
Saya tidak berpura-pura menjadi lengkap, dan saya sendiri tidak memahami beberapa hal dengan cukup baik untuk mencoba menyatakannya. Berikut adalah daftar apa yang dapat Anda pelajari sendiri, mengetahui prinsip-prinsip dasar:- Lingkup
- Penjilidan instan
- Mengikat multi
- Callback yang sudah ada
- Sumber eksternal
- Hapus perangkap versi
- Kodein yang dapat dikonfigurasi
- JSR-330 Compability
Baik dan tautan ke dokumentasi:Terima kasih sudah membaca, semoga artikel ini bermanfaat bagi Anda!