Kodein. Dasar-dasarnya

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() } //   } val subKodein = Kodein { extend(kodein) //   } 

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 argkelas ke parameter Myang 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() //     Gson 

 val myTrigger = KodeinTrigger() val kodeinWithTrigger = kodein.on(trigger = myTrigger) val gson: Gson by kodeinWithTrigger.instance() /*...*/ myTrigger.trigger() //        kodeinWithTrigger 

Lazy Kodein misalnya pembuatan


Sebelum itu, kami secara konstan membuat instance Kodein, tetapi dimungkinkan untuk menunda inisialisasi properti ini menggunakan kelas LazyKodeinyang 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() //     kodein    

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

Inisialisasi Kodein Tertunda


Untuk inisialisasi tertunda, Kodeinada 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 = /*     Kodein */ /*...*/ 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, KodeinAwareAnda 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 Kodeinmalas, Anda dapat mendelegasikan inisialisasi instance Kodeinke 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, providerdan fungsi serupa. Anda DKodeinbisa 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 DKodeindalam framework DKodeinAware, Anda bisa bereksperimen.

Dapatkan dependensi dalam konteks apa pun


Untuk mendapatkan Kodeinbeberapa 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 Kodeinmenyarankan 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 Activitydan 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 Activitydan Presenteruntuk 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 OurContextClassadalah tipe kelas, sebuah instance yang akan bertindak sebagai konteks. contextedhanya 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 Kodeinmelalui 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 Kodeinseperti 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 Applicationmengimplementasikan KodeinAwaredan menginisialisasi properti dengan Kodeinmalas (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)) //  } val inflater: LayoutInflater by instance() } 

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 Kodeindari Application.

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

closestKodeinAnda 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 Kodeintidak 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!

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


All Articles