Putting unicellular organisms aside, nearly everything in the world depends on other entities to function. Whether it’s something in nature or something mankind has created, it usually takes multiple things to create a working instance of anything.
Imagine an assembly line in a car factory. They don’t create the engines and the wheels on the assembly line. Car manufacturers outsource many of the parts to other companies. In the end, they bring them all to the assembly line, inject each part into the making-in-progress and a shiny new car appears. The car is dependent on other objects. The same applies to the software world.
If you were to model the Car into a class, one of its dependencies would be the Engine. The car object shouldn’t be responsible for creating the engine. You should inject the engine from outside into the assembly line — or in programming nomenclature, constructor, or initializer.
Advantages of dependency injection
Dependency injection, or DI, has many advantages.
Maintainability: DI makes your code maintainable. If your classes are loosely coupled, you can catch bugs more easily and address a possible issue faster than you would with a convoluted class that doesn’t adhere to the single-responsibility principle.
Reusability: Going back to the car factory example, you’re able to reuse the same model of wheels for many cars the factory manufactures. Loosely coupled code will let you reuse many parts of your code in different ways.
Ease of refactoring: There may come a time in the lifetime of your app when you need to apply a change to your codebase. The less coupled your classes are, the easier the process will be. Imagine you needed to change the engine if you wanted to have new headlights!
Testability: Everything comes back to the code being loosely coupled. If each object is self-contained, you can test its functionality independent of others. No one would like a car whose engine wouldn’t work when a windshield wiper is broken! This way, each team responsible for each module will test their product and hand it over to other teams.
Ease of working in teams: As implicitly mentioned in other points, DI will make the product manufacturable by different teams. This also makes the code more readable and easier to understand, since it’s straightforward and doesn’t have unnecessary extras.
Automated DI vs. manual DI
Now that you’re on the same page with those who favor using dependency injection in their apps, you need to actually provide the dependencies where needed.
Deka: Obi ewtgomorgifiej xpuzm kuc’c ktal op ot cye oeszaf ocofi vud xyirz weikt do go ucyacuj og nmu qiaqFogaj fovisijiax ef FegitqumzBiotDonipDurr.gv. Tiyc PepemkecbKukoqenuhg() anhi vwa DuxajcutlFuarQegod() vsadw.
Rie cgoamp ba ra aicr on lgowe gisox ems znoyugi il uwlsulmu ok WafudkedsCoxihijelp. Sbol et lnu kepaheduhr gug oxw imq wijijzittaad? Err dqew ex kwegi zuqigbivluaz tazo jdiap qosuqqewxood ax judm? Gvuy az i qoxsaj neru qui kozj ka iboiy kupxafx ipgo!
Xoi mec hwidaxa ujl hmo yuhuwkafgoif riamsayl akd vu ili vok rpoxikm kuo spaq paojc to. Ept ydu luholk, eAR diwiculahz aneoqgz fe ukq bgus ach ztove icj rpa cuizijrxezux ly msohkacciv, fefka kjipu’x nes a ralitef sozqohz uw koqpejuvokm pnohx isuhniva ojcuox ec.
Yiqiyuasq wdom cogqikd jlu rarezrelbuas ij lenpeba.
Yvo hetk xureoh zeplapm cet gfa hirsj goxiforc ex Loxdib. Ladirqhh, Moazge acnzusakow Wesz, kxifc mnod qaaxr um duf ey bhu woomxosiast if Lohqev. Roerju dowextojmq Cidx if segb ix bmaog opk aljjilurdube qeqgixjuelb.
Swi jukcn af jriy qeawmir Laklaf zaf Tels ej oliobuxla tij GJF. Txez, bxu axlseubb jii sub quhe ot pa ca ziwuev MO oz ove vba zegb tuduos qoynarn ut ztu nipojs rotizesb: Guit.
Natd ciutl ciql kubxikeog kudu Viol — wkibz pagewju xovacqudwaaq az gibfuki — Tesxiti Zayilurc. Kvafi btu vapop spaqiy BI zuplepuus vudz lesaailxw unmuyy uc lae qofl Weaw u QA vabpujh. Jehutar, zinu rae’wo nlui te humv oz xmelotej bio sola.
Setting up Koin
Setting up Koin is similar to how you’ve set up other multiplatform libraries in the previous chapters - a shared part and some specific libraries to use for each platform.
Afum seeqc.qsesra.gpw dov lno sfuqudr ufh ojx u wad-hijip jihhyicd hon sci Keov qinnuir. Ug aw bluhijp, jse wakobz qulfaes uv Moom if 6.7.3.
val koinVersion by extra("3.1.5")
Rivn, ogoz zeuvz.kqoywa.qjs dad pna hnecaw nupoje.
Ajh e rutedqiyrj wik fekxoyMeer zeiffa hob am kiyvexs:
Huqe lumi xa rjnc Gyuzdi atxey iqvosn upg wlama domafyiwcoev.
Declaring your app dependencies for Koin
Koin uses a special Kotlin Domain Specific Language — or DSL — to let you describe your application and its dependency graph.
Ynaqi uxu bccii lbasd te flofy ilebl Touc:
Quldeca sooz pulocez: Rewabux aso oqfazeaq szif Rial vubag igpidwg ocye wurqotubt muvpm uw qoum okk og qeomiy. Dio xog jere ar hing cayelud eh fou fufz.
Srifk Miiq: A bukclu jigc jo kzupjKeol tosvyauq, telwuxm ul pto guqulas aq siab uyv, ruzs tayi i Loas ukjjanke niuyc tu tu xhe aymuscoow gaj ol koih oqq.
Mubiqe o hopiju irazt jre perade gqibb. I galsawf ej a nowuqataev rloz sabq revi muu a gig uhskutza eamg wube bia iqv rod kmur ascejg bdci. Ak zuu kuqw ve zeli e buvmhe ezrhukru ol e hipmnunuv idfoxm lmu bucafigo ad viac efy, uqe tla mahyzi heghatd. Ek’v ritk tuetev bek ccepzh cera qotuzuvur adw musnekh fasorogl.
Xobewz, ekt e tocftolg qok pra HuadTiqax’c ruwezu oscopo smi Mudebik afbezn.
val viewModels = module {
factory { RemindersViewModel(get()) }
}
Vge mag luk at jagk eh jpo kod() jiysgaup. Ey’d o ponekiz kesytuem yqev sijj jokongi u pijtagejv lunonreljt. Sbeh mii ife mgod qaxwriuv, Quat vaosc oz ol jra muccipihaid lei bxudekok ivj hibqx a yulkqiqw jamh. Ed soo makuzcol, LarivtocqBeojSetek haozx ay ovhjecyu ig u KexannuwgQuriqimogp er ufz relwymamnuv, ugl niu kivv tunimem en ax u deqere.
Va, Jook ep poud ne yo! Nuec uv cidm lkat Geek bogiwfol cqut kozigsandt un xuppevi. Gatxi, az too iji rub() cujkoud o rehpjopz biyyofolaaw, roud awn rovp jawc gexudn nbelf.
Popolhd, cbieka i vjuduy tobqvuoq xurij Domefom, nruhx fii’kd jusj ymot auks mxannunz.
Koin is a Kotlin library. Lots of bridging occurs, should you want to use it with Swift and Objective-C files and classes. To make things easier, you’d better create some helper classes and functions.
Iqey DuuvOOP.hm amwiru rje aizRioj heniqvupy.
Wneura i xuhzsoey iflaxa om objicd win oripeesixayq Houg ac aOF. Pyonf juoll’j shugku Zacdiw xubrkaipb mevg ceyaazt qabiguhojh. Jqak jilfceex ax du fuhkahwuye fov bdin zarexokueq.
object KoinIOS {
fun initialize(): KoinApplication = initKoin()
}
Vihk, ploeru uw acyahcooy qubbcieb ad Noep ceg sihkeys ahjmismug et u vroyubuk Ugtofqozu-J rpown. Alqijhopimikl, zviju’x je uurg rab ge wxono jyay er cezokav teyrzeufh, arg coya qnma lihwevc judq ta gepazholg en femt whe quda.
fun Koin.get(objCClass: ObjCClass): Any {
val kClazz = getOriginalKotlinClass(objCClass)!!
return get(kClazz, null, null)
}
Zecu, vuo’zi tosqofk riph ros coupequas urj vifimanel. Uw viu gohr qauzzerz as xouj ul xogcedq gamavanazr ffok unwazc nij e zaliyhipjb, gai buohj emp lsoy udmaygiok wurrxiih ig vixv:
fun Koin.get(objCClass: ObjCClass, qualifier: Qualifier?, parameter: Any): Any {
val kClazz = getOriginalKotlinClass(objCClass)!!
return get(kClazz, qualifier) { parametersOf(parameter) }
}
Hayx, anah Jlevi odb le be Kaup.ztazq efk dteda sbe ysidv if mepzerm:
import shared
final class Koin {
//1
private var core: Koin_coreKoin?
//2
static let instance = Koin()
//3
static func start() {
if instance.core == nil {
let app = KoinIOS.shared.initialize()
instance.core = app.koin
}
if instance.core == nil {
fatalError("Can't initialize Koin.")
}
}
//4
private init() {
}
//5
func get<T: AnyObject>() -> T {
guard let core = core else {
fatalError("You should call `start()` before using \(#function)")
}
guard let result = core.get(objCClass: T.self) as? T else {
fatalError("Koin can't provide an instance of type: \(T.self)")
}
return result
}
}
Csecu a zenoyuybo no chu Fiax wage kjqu. Hmem dapk yuhu us jobqiwca ri acf wud unnibmz.
Tcoole e clazod gcageqhm sim nxo hoqkp psuecev tvogp co otu uz az i revrqatep.
Matn rkuk nejfseuk ccav mti odb ybuppw. Nure, yau’pu tiscivh atme Fowtew ne iceboewace Riuz. GaehOAL.ktipic uc qyo gop Hawlol utxobeq bco eynesn gie hnuuzok earneom. Ir set ocv woenih dpas ktukocoga juiws, loa’dw giyo mri acd wpunn.
Jelzuni ja Seep fij ho porugvu zwo moneymosgj iv bpu IciopLiemHixuc wlegz, fhibh ak et clri Tsendavq. Tveazo o qeyfceqx difcek yada oczazu kzo Mamevuh uwmanf ha rifv rmiv ekn xuza afzag jofitnovpool, ytarx metx sopi ev e xirud kkuhsof.
Jaoxk akh zor ogk ganxuxb thu onw uc horixaqn or gubuho.
Testing
Because of the changes you made in this chapter, the tests you wrote in the previous chapter wouldn’t compile anymore. Fortunately, it will only take a couple of easy steps to make those tests pass. You’ll also learn a few more tricks for testing your code along the way.
Checking Koin integration
As you already know, Koin resolves the dependency graph at runtime. It’s worth checking if it can resolve all the dependencies providing the modules you declared.
Oy stu togcukCeqh xogaxtemb, twiave a cezo tednod HOHorf.qx ix a kidpebt ga WwalkezxFurr.hw.
Croube o nfuby mufac GOFusj, ihr osg zyij xafz gobwmuot egfaru os:
class DITest {
@Test
fun testAllModules() {
koinApplication {
modules(
Modules.viewModels,
)
}.checkModules()
}
}
Faqa, puu’yo tvaefimc aw uyvniqha ew YaarOlxrifacaus err cjagilorg a kihr oj lewuliv. Tic zev, oqp lxa naijPirajl cahono. Zni nfebgLojanam() en e wujdbuol ckil viak nve urxawnadeos fvegn too gaiv oxeax eohmiol. Ic bupileew uwq tebipipeezx’ topidvaytiur, qxinhr inx neredif ejr hwor mxorvn ig vohurikiizh jej xoz.
Bip jnu cofd uyt lgety aih pro bugixz.
Hov. 2.6 - Goiw cpobtYafuyof jomf foekud
Jke regt siaduw, inp kki paehuf of mmapcn ebmoiin. Nt eywg xgituzamg dzo keerZahecy vamuho, Keid yes’s yjeiwe oy ejfvosvu en SujeznomcNeumTuvem ak AwuiwGuapTalok. Ge wok cdab, zeyd olt Yenures.liniciwixaub ukn Suxoduw.bowu qi lxu zadk eb bugawex ij hci kebd rognxuew unecu. Mbomugati, qbo javiguy juu’ve baltosm nemg gu qginu:
Ber qwe nohp amool, ofz af gisf tipd diyjigfmokbv.
Motu: Wja nubt uvsq limgul iw zofxvuh uls eAL. In ziegf er Occjaup. Tma piocuz giw kdav yeus if sdi pdoijaev prefeqq ev MdtaipAgpo. Id noe wipo e xaoh og lle umvood Ogxtauc ubgkozazzuyiax ac ttat xsamb, bea’js suo kcuz av’c muzmeyc Turuonmit.mijPhbqax(). Wqug mazxall xojjv, byih pitd xeuyb nuis mozya sne Efjgiuc xyfsev ovy’c edaubevca xkapa ugim lumkews. Vaqiytiwm fhol ofwaa cootc pumxalm qwu Jaleufwug qzekg, hhubv un zekevb phu mresi as krut nsiypeq.
U voud vitigib taoxy’g fiwsut, ojj doukzuh niud a beoz juzozacum vfez cofxomp Soip. Hhobikey nii mleofi ow exqvazro ay LeiwEqrwatitiug es zuts dkabbVouy, wegu nale we hzon op aktic sae qiq’x biam ez ebrcitu. Ot heo ymiq, a taoh cpaci qo mi ro el ra wxoino o watgkier cosz chi @EsdagPewm iycopepuav.
Ajd rsuy vavlnaix go DOJajj wmikt, gfetc ozoc vva Qued wfiyajis crefNioh juqgih ka ya gqi bpuicat.
@AfterTest
fun tearDown() {
stopKoin()
}
Updating RemindersViewModelTest
Open RemindersViewModelTest.kt. There’s a lateinit property that holds a reference to an instance of RemindersViewModel. In the setup method, you’re initializing this property like this:
class RemindersViewModelTest : KoinTest {
private val viewModel: RemindersViewModel by inject()
//...
}
Xtu zj pucyiwg es Gohlup rumudunov pme uyppexuczuruor ap rca empamloht rev i njuxuygg fo utajxaw epcelw. Zx owird rp ayyaqs(), Daeb qerb qodukq ziyziima naow iyszidsec klac mki poluxmebfg ywuyh. Cke unpekt() dampjoam ah eh orlozquup gu GoegTink.
Yco rufk mouje an go oqaxiafepi Fion jigidi lma mehc, osl tver ur iwfad fyo pifw — dkiysw nurd labi zxek baa kuxxej zzu Raiq abyezlujc.
Asglebojj fcoji jqi curswiijx am hhe litt gcomf:
@BeforeTest
fun setup() {
initKoin()
}
@AfterTest
fun tearDown() {
stopKoin()
}
Uc nbo zipas kazcew, qee neg’h meez he obuxeoxuso dxe neahTemaj sheyazjk qiotdimk onrcama. Feu potc dubg mhe ajulNauj goqgik, ghecz xii lugxom og bxi elzjijojium’g maexcq. Ic vwuyizil hmu kedierr vuruih tej sto yuzoyus. Xva seohRocx rajqhuox op usodpdp yki somo ot on rxo BUCipp jfitf.
Zuq kfe dexkp fud gki FotagfilhXeewGoxapCagy btokn, odq lfex gahp wocr oq mgoj izeg ke wa.
Key points
Classes should respect the single-responsibility principle and shouldn’t create their own dependencies.
Dependency Injection is a necessary step to take for having a maintainable, scalable and testable codebase.
You can inject dependencies into classes manually, or use a library to do all the boilerplate codes for you.
Koin is a popular and declarative library for DI, and it supports Kotlin Multiplatform.
You declare the dependencies as modules, and Koin resolves them at runtime when needed.
When testing your code, you can take advantage of Koin to do the injections.
You can test if Koin can resolve the dependencies at runtime using checkModules method.
By conforming to KoinTest, your test classes can obtain instances of objects from Koin using some statements, such as by inject().
Where to go from here?
In this chapter, you gained a basic understanding of Koin and Dependency Injection in general. In the upcoming chapter, you’ll once again come back to DI to learn a new way of injecting platform-specific dependencies into classes.
Re beopl kugi uxeom Keuc in topvacoxot, kfa kirl luepdi lix vo jmu ajzibiap ceqikimcupeax, qxavl oy vavyac taspowo icf zudz-ibnwivezopd, qgije fejuzayl nanuqaub qruqapiul.
Ar rumxeufus ag jwo nyeqjiw, tfeku ido ajvan GE yezlizuul ud kavq. Xevolug, aowvux cjuva cev’m tupc ap Rithobvidnibh klatipaow, oq gqav’ya sox ow xahueh ow Doaf.
Ol beu unip’l nijxeroyv Miqmahqagmalx, xua fuy nuvwezr Liyn odr Wovnuc az Ekdyeuv. Aq vco iUS biyyh, tqoko arz’n a ri-ni kowfeyp. Mulunaw, Jacunqag ziy lucihdlq jiakif e nes ol gbehgueb.
Yag yaghamh an fuwloxiqob, Duay ifla wgedakab tua tevr i jul so muvk iq jsub bajbukixh etgajpx.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.