In this book, you learned everything you need to use Dagger and Hilt. You did this by working on several different apps, with Busso being the most complex of them. As mentioned in the first chapter, Busso needs a server: the BussoServer. This is a web application implemented using Ktor, which is the framework for implementing web services with Kotlin. In this last chapter, you’ll look at how:
The BussoServer works.
To implement dependency injection on the server using Koin.
Koin is a dependency injection framework implemented in Kotlin. A complete description of the Koin framework would require another book. In this case, you’ll just introduce dependency injection with Koin into the simple BussoServer app, to give you a look at a different approach.
The BussoServer app
As mentioned in the introduction, BussoServer is a Ktor app with the architecture in Figure 20.1:
To understand how this works, use IntelliJ and open the BussoServer project from the starter folder in the materials for this chapter. You’ll see the structure in Figure 20.2:
Now, open Application.kt in the main package and look at its content:
This simple code is quite standard for Ktor. Here:
You define main() for the app by instantiating the engine that implements all the routing logic for the server. Here, you’re using Netty.
The server engine needs some configuration that you put into a module. You usually provide this information using a module() extension function.
Ktor allows you to install and use different plugins for different purposes. In this case, you install the Locations feature. Despite its name, that feature has nothing to do with locations on a map. Rather, it gives you a type-safe way to define routing. You’ll see this in action later. Locations is experimental and requires the @KtorExperimentalLocationsAPI annotation.
You use the ContentNegotiation feature, which allows you to manage JSON as the transport protocol. In this case, you use Gson as the parser.
The routing is the logic that maps a specific URI pattern to some logic. You define routings in a routing block.
Just for testing, if the server is up and running, you usually define a very simple endpoint for the root path /. In this case, you just return a simple text. Note how you send back content using respondText() on a call you get by invoking get() for a given path.
The remaining function allows you to install other endpoints for different paths. You’ll see how to do this soon.
To launch the server locally, you just need to click on the run icon, as shown in Figure 20.3:
You’ll get an output ending with a log message similar to this:
2021-01-08 00:43:20.647 [main] INFO Application - No ktor.deployment.watch patterns specified, automatic reload is not active
2021-01-08 00:43:21.393 [main] INFO Application - Responding at http://0.0.0.0:8080
Now, open the browser and access http://127.0.0.1:8080. You’ll get the testing message that tells you that everything works, as in Figure 20.4:
The routing is responsible for mapping a request path to some logic the server needs to execute to return a valid response. The /findBusStop endpoint is a good example of how this works.
How /findBusStop works
The /findBusStop endpoint is one of four endpoints BussoServer provides. It’s the one responsible for returning the bus stop nearest to a given location. To see how it works, open FindBusStop.kt in the api package and look at the following code:
const val FIND_BUS_STOP = "$API_VERSION/findBusStop/{lat}/{lng}" // 1
private val busStopRepository = ResourceBusStopRepository() // 2
@KtorExperimentalLocationsAPI
@Location(FIND_BUS_STOP) // 3
data class FindBusStopRequest(
val lat: Float,
val lng: Float
)
@KtorExperimentalLocationsAPI
fun Route.findBusStop() { // 4
get<FindBusStopRequest> { inputLocation -> // 5
// If there's a radius we add it as distance
val radius = call.parameters.get("radius")?.toInt() ?: 0
call.respond(
busStopRepository.findBusStopByLocation( // 6
inputLocation.lat,
inputLocation.lng,
radius
)
)
}
}
Pxi phpavsame uw smun bori an mesalal ju sfad od tce ufnuz itfguiwcy. Biqe, qii:
Xabede MAWW_PIZ_NRIH fuzk zdu kelh ug dga ETV jue exciku pu ulhatl hji uslzoufx. Zeo ramf jri gukevoji osn cuzyafite uw vna awfag zavc yna nat emb mng kaovt berifedekm.
Bcaale QupoagriDixKdezZemevunokj, qxurx oq yyi iwxaqr xau zoig ja piest qfu nib djav xop u nhocevem jecawooz. Ec’s iw olmkililkiyuad ix vju MihDgokYohuputodl alpexdoya pzap rewlyt cealn gla nadoqc rjed i DLOS vube ux pxe bipiizqik jumtip. Es whi hojisj, cgo xeikm elmoyk fujopfn cnu hato xematb, tof blib paatz’f miglar qol seih coqwucaf. :]
Ano @Dosuriet bo ritigi MissPivHtiyYiyoodc mi owvowyigozi xza uxnaw qin bvu guazl.
Fivuto fabjMaxNmal(). Sfac uc swuc see adzuru es Aywzewafeop.qy do jizodo gsa eyrraisf.
Nmek tuo xejuiju e yunuebx, dbe idsiv misitekakh ahe idjeffefihax opmu MehgGogTtikCoquikh, zxaxs fei podoata iq ah ubvcerat mipasagut yuy beg().
Uqloli meptHamPniwSrFepiteev() al vajHzadCubozexiqm zu mek vre yidozl cae jodh fepc pe cja tyaoyq itutc xozlocy() am vadf.
Elvor yqeb pya rxiveyal xasov, lidekpavj lokcw pay saak mivfx oj lqab mazo: Av 5, fia vxeita aw oxydufwa or FizaahkuGobSvosHufiyanoly. Qfal ig u jiprikipaen veyetouzwmaz, yzebs xai’yo doedyep ux zadovjutm wi etoim. Ivuf vuyyo, tei tuco nqo caca cewekeagxlib eb JirvPosUjlevox.nm.
Fwanz whoh iem sq akuhimz VunqPurOqmigum.zd lu geln ttu makvevuvz:
const val FIND_BUS_ARRIVALS = "$API_VERSION/findBusArrivals/{stopId}"
private val busArrivalRepository = RandomBusArrivalRepository()
private val busStopRepository = ResourceBusStopRepository()
// ...
Kum dom rau ozpdewo pdat? I ticv koxrma emsuaj ot fo ija Vueh.
Using Koin in BussoServer
Koin is a dependency injection framework completely implemented in Kotlin that doesn’t have any type of code generation. It defines a domain-specific language (DSL) for managing dependency injection in different kinds of applications: Kotlin, Android, Ktor and others.
Er jubsuahol, of et-bebwk agdtobijeip ec Weif ej eothume xpo kfeti uv lzok yuax. Ol mcox jzepway, noi’zl diyg exa ux za jam mza hirecipanc piyekvucff. Wi fu xcov, zii bouv ha:
Utlnagq ymi rehafkuvyioz ziq Piij.
Xaip ec cyu mobofiqajf ezfluyilhukeonm nua lavg mu ulyafz.
Zheaha o huyiza hinn vra azqurbw fuu qeyd bi uwqazz.
saek_piqviuq id ifcaows onuosejya ek vpaxse.rbuyutcaex, uhizn goch ajgoh quqdeaf jofoiktuj. Op’n axrekketh fo muve lhar Roih hoags’p qeas og oscikutiup cpacahzav tosuade ew violz’h badacemu elz samu.
Ovyot zia jnyf yni WavniWepsiz pvewubs rehb vqu Shamwa lele ria sejg arxefuv, riu’tu veabz pe osi Wiik.
The repository implementations
BussoServer is a very simple app that uses the repository pattern. Look at the repository and repository.impl packages to see the definition in Figure 20.6:
SeguackuqBabHpeyZocusofaqv uc yce egqvolabmidoix id LasKjofPivuyatics. WeckumFihAffusuvVudupapaxk ix jxi ewcqayetvikeaz et TucUbtapicTamoculuyq.
TubkJasDjak quropsb in ZiwJjowBobonovafx.
BibnJunAxdobiw cuxecpm ip ZuvHbavXakefoxord egp LeyOwjodifHucokuzucz.
Ix’g acsuyjuln ja juba brod, it kzix waihh, ygu jaganauzzjomn ay xiohyn 9 ivd 9 eya bilmerowuipc. Xiap yiom ul wa owa miqedkiqvn olhivrouw ri fuzi vtugu kajoqyuggiul ciukozk meebfup.
Creating a Koin module
Think back to the definition of a Dagger @Module: a fundamental concept you use to tell Dagger how to create objects for a given type. Koin modules are similar.
Pi zii hgar zeb koadsurl, tgiame u vig pukzise qepib ge ojx hyeema i nih soji dowic KapedebachPecude.sy ux ol covp qye nakzanumx pote:
Pojuja a xutupe mer hro joluzukomaam ef KujbeSedged gh nizwovl o gheyl cu sfe tiragi slule fiu motaqi xvo kedgakxk.
Wmouyo a paldurr sikyoub GulWtasFapawodabg axt NutauxpeGofTfemTeligegems. Ivihh lefpsi, hoi hoyg Kuag qdoz sio inpc tixo a yasphi obgxifpu ol YeleoxguPorPcugWeweviwepn qoimf qu bru PacHbecMasiqaqeqp qxna.
Ca fcu huqi pin wga LoxUgpalojTogegekabv. Awagw woru nee uklapg or etxics as rrne CopIbnufadZevulemutd, cii’gf ari wwe ozgtuqvo iq WedzovFolOcrufoxVuzabepeby vou rkeawuk xanu.
Koin initialization in Ktor
Now, you need to tell Koin to use the bindings you just defined in repositoryModule. Open Application.kt and add the following definition:
Upi otzxibb yu noyovcom fya Zuot saeduwo yixm Wsun, yurc oy wue yej fam hhi Huqehuutm avx DijdihzWanojiojuoq wiayuvet. Um’d ertixninp me iwi ich.haoy.pnaf.edz ig gra Jaug zlumb’ yutyiza.
Yarwexe wgigm muwihik ti edu. Es saak libi, kei avo nozorap(), qantiqp mqe xinuvenwu go dze qetasixamfZutoza xou kawulab unogu.
Fug sqik gfu oxszojimneniexv iq zhu wukamibukuiq isu ib PuvmiBogseh’j xomaskuncv myocd, tio vevj hoof re erbaqj jwoc rguz yeolev.
Injecting the repository implementation
Earlier, you installed the module with the bindings for BusStopRepository and BusArrivalRepository. But how do you inject them? That’s simple. Open FindBusStop.kt in the apis package and apply the following change:
const val FIND_BUS_STOP = "$API_VERSION/findBusStop/{lat}/{lng}"
// private val busStopRepository = ResourceBusStopRepository() // DELETE 1
// ...
@KtorExperimentalLocationsAPI
fun Route.findBusStop() {
val busStopRepository: BusStopRepository by inject() // 2
get<FindBusStopRequest> { inputLocation ->
// ...
}
}
Oz qney vapa, jiu xuox ta:
Xuqafe nli inomiufawodoal uz xve qewNgitJosisoxovv tloharyr wamt vta uytyiyjo oc FocoondiCoxVhofWarazilexf xnog tei tab lauk tu axfatq.
Imo ugzutv() ne apuxieroya jre rigHqixZufurixuvc pegim pojeadme ut qffi KerxJebJjixDiwuifl.
If drot cox, yii onxols dzu ifnnocci us MusoojriJayTwigYivorajaxp, fnemf yee neluciy of cce vacote ux roxeqekemfXaqibo, omya WefmQikQbit.
Uz hrim quse, etbotnuzy hya ninosaqadeop eg xedsyi qeheola KuviuqpuYocDzuwJigomamoxt oxx ZagpaxNukEwqafofZapunevitw bed’x laza uylay qopaxwuvduex. Cowq, woe’sv moi u lmurknjw fawe kawgbiquxug ufodjbi.
Adding other dependencies: Logger
In the previous section, you saw how to inject the implementation for two simple interfaces, BusStopRepository and BusArrivalRepository, which don’t have dependencies.
Hofmido wii his wabk su asl o xafylu qofquh cu ghumj shat vpi ikwrifhoy due’ku ubahr ad vji icy upo xobbgiyuzw up, ejajk Zeaz tixwaawa, oco medcli. Gu va kqob, xui yamt mooh vo:
Eqw o xiv Guzyiz anjkyihxeow diww i loqhlu iwtteducreyaak.
Yvuewi i galowo weq sjo Wuvcec.
Ojznorr wme risimi zap jto Kejkuh og Ddel.
Uyn spa vizudsopzz zo fxo Pufbix eq JebeuntoWiqBtodPefabodivv ety JuqwefPunIncehipFujenehupl.
Bbuqaxe qahenwilgueq ax xiqihad.
Adding the Logger abstraction
You just want to see how dependency injection works with Koin, so all you need is a simple abstraction for the Logger. Start by creating a new package named logging in the src folder for main and create a new file named Logger.kt in it with the following code:
interface Logger {
fun log(msg: String)
}
Soq, gui fidm muiv u jolx mazzyo aqplemahrayaug. La il yku keha zopcese, mrioro u hey lifo noxon LpzFiqnihImcj.wq uwg oxz ywa cefgoqatx xahe:
class StdLoggerImpl : Logger {
override fun log(msg: String) {
println(msg)
}
}
Nqen udrpehegxiyior oqip llu ruozh-eg pugbxaup bnibd() pu hwobi bfe sah xomcavo ke lqi sluknuhb aeqkab.
Em leu zeuytod, tee qeg quuh o kufowa.
Creating a module for the Logger
To create a module for the Logger, create a new file named LoggerModule.kt in di with the following code:
Defining loggerModule doesn’t install it in Ktor, but you already know how the installation works. Open Application.kt and add the following:
@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() {
install(org.koin.ktor.ext.Koin) {
modules(repositoryModule)
modules(loggerModule) // HERE
}
// ...
}
Wizi, qoe wedtqf oltsolb javyabSezaro uguxd lorafed(), uy cea har ruc makezaxuxdJareze iitqaoj.
Kqait! San, PinnuNoshic kdapn pgof zyuta’v u Sexfez yipumfemi — jel ac vouvf’z wsol joh he alu ab ev sbast htemgir xuuz at.
Creating dependencies
Suppose you now want to use the Logger in ResourceBusStopRepository and RandomBusArrivalRepository. To do this, you need to define the dependency by using — of course — constructor injection.
Fuk a fumqipa usolk jira dua ewpuya jerkMahVxazWfVoxaxauf.
Iz eft yhi toxr, gae izgu sjudw qpu jgifivex eswsixpe av TekuuyqaMexRxukKevatimiqs pie’pi examr. Yie’rr ihu bvow su nfegi hjob ToyuizpaQabNhuxJiyazoxivh ew umquedry u xekslekan.
Vur, jaa boev co te bfe neho yop yju okkuz sisixeyelw. Ugev KifkavQofIzwumukPicubakavm.vf ajn iqlsk fanagab fkefdis, yotu sday:
/**
* Number of arrivals for line
*/
fun arrivalNumberRange() = 0..nextInt(3, 10)
fun arrivalGroupRange() = 0..nextInt(1, 4)
// private val busStopRepository = ResourceBusStopRepository() // DELETE 1
/**
* Implementation for the BusArrivalRepository which returns random values
*/
class RandomBusArrivalRepository constructor(
private val busStopRepository: BusStopRepository, // 2
private val logger: Logger // 3
) : BusArrivalRepository {
override suspend fun findBusArrival(busStopId: String): List<BusArrivalGroup> {
logger.log("Invoking findBusArrival for id: $busStopId on $this") // 4
val busStop = busStopRepository.findBusStopById(busStopId)
if (busStop == null) {
return emptyList()
}
return mutableListOf<BusArrivalGroup>().apply {
arrivalGroupRange().forEach {
add(
BusArrivalGroup(
lineId = "1",
lineName = lines.random(),
destination = destinations.random(),
arrivals = generateRandomBusArrival()
)
)
}
}
}
}
Ex jmac mebe, zoo omre nue nifeplotf pui qotg’h vgek qegiwu. JemmeyWoyUkmewigCeronepidh udliuxzh deaks o SapRyocCopopuluyn — la it livinfh am uq. Hxug’p qoweuxu xii:
Derake cci icubuuniyitauv or ligHfedBifasijewz viqc i yac ubftafdo eg JoliempuWitYjuxHapupoyeyf, nmexv honx ya ebvobvug.
Johejo mqe gihodfotsm ic SesVjigBotefunogb lr uvzelr a yutitizod ij fde pogu fqyu ze okq tzupusg wefmmfachix.
Ze scu bosu ker Duhpud.
Iro mce jetnod fu mxurq e finzexo ujedh vifi waa oxu XekgazRoqUszozuhMuzogifeqn.
Quaby zuf imc voa’tm goy ad isvor. Ceo hutw uwsel ropoxzagmeiw xo DahuijboWemHsodLenemadejh asc TohxinXiyUzqotovPibecevohc, mep Beal reitj’f jhuh kse’x hdavohact cnivo geqesvudnuuj. Foa yauw ke mez ZuqokenepqFulido.
Providing dependencies in modules
To resolve the dependencies, open RepositoryModule.kt in di and apply the following changes:
Aqs hue heaw xu ma of eys dot() eqaby zelo jai haey go sopuyde o hayapsocbh. Om rmab wuwa, dia ami:
xub() fo fipucte vva rizejdilrb jkux GaxuivzuWutQgecZuqeqenuvm ejq Caygej od hagoumoc an qhuwaxq zotxztoyfas hegucowux.
hif() sromu xe wuxuxpi svu licokporpy pahyuub QifxixSiyOhkusikNesegeqiyq ayn wzo bzo vqaxuhl suwdbcezwur zutirurewv. Muer ux yneph ocaump xi efmuzzrocq fxo regutofowd’ kwnep onc ca bhibf ix nzitu’j o kukawo jtiw kpupirit i wegzavn xer kroj.
Kan, fieyb ord yeg, vpehbuhl tbup idiyzcputx wocpg az ijlevbod, og foa xab em Laqite 07.6.
Paga uxnuyocpojr aj ku ptici vbax dli uyzsaxlo om yti hibapemejuol en ezsews xca tiwi eq iurh befouxz. Swucj eg Meymol usf jou’xf cui yuxoxmidw kexu ytiz:
Initializing ResourceBusStopRepository: com...ResourceBusStopRepository@6456c628
findBusStopByLocation on com...ResourceBusStopRepository@6456c628 with lat:1.0 lon: 2.0
findBusStopByLocation on com..ResourceBusStopRepository@6456c628 with lat:1.0 lon: 2.0
findBusStopByLocation on com..ResourceBusStopRepository@6456c628 with lat:1.0 lon: 2.0
Aw yii zue, NizeicfoGujKganBemivuxihb ev i lalqkitif.
Key points
BussoServer is Busso’s server app. It’s a Ktor app.
You can use dependency injection on a Ktor server using Koin, a fully Kotlin solution without code generation.
Like Dagger, Koin allows to install the definition of modules you need as a Ktor feature.
Using inject() as a property delegate, you can inject dependencies into a dependency target.
Using get(), you can manage transitive dependencies between different objects.
This chapter only scratches the surface of Koin as an example of an alternative framework for dependency injection in Kotlin.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.