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:
Figure 20.1 — BussoServer’s High Level Architecture
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:
Figure 20.2 — BussoServer’s Project Structure
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:
Figure 20.3 — Run BussoServer locally
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:
Figure 20.4 — BussoServer is working
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:
constval FIND_BUS_STOP = "$API_VERSION/findBusStop/{lat}/{lng}"// 1privateval busStopRepository = ResourceBusStopRepository() // 2@KtorExperimentalLocationsAPI@Location(FIND_BUS_STOP)// 3dataclassFindBusStopRequest(
val lat: Float,
val lng: Float
)
@KtorExperimentalLocationsAPIfun Route.findBusStop() { // 4get<FindBusStopRequest> { inputLocation -> // 5// If there's a radius we add it as distanceval radius = call.parameters.get("radius")?.toInt() ?: 0
call.respond(
busStopRepository.findBusStopByLocation( // 6
inputLocation.lat,
inputLocation.lng,
radius
)
)
}
}
Heads up... You’re accessing parts of this content for free, with some sections shown as pjbanklyw text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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:
Heads up... You’re accessing parts of this content for free, with some sections shown as qszyxttyx text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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:
constval FIND_BUS_STOP = "$API_VERSION/findBusStop/{lat}/{lng}"// private val busStopRepository = ResourceBusStopRepository() // DELETE 1// ...@KtorExperimentalLocationsAPIfun Route.findBusStop() {
val busStopRepository: BusStopRepository by inject() // 2get<FindBusStopRequest> { inputLocation ->
// ...
}
}
Heads up... You’re accessing parts of this content for free, with some sections shown as nmtuhztog text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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.
Heads up... You’re accessing parts of this content for free, with some sections shown as kjvasfcup text.
Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.
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:
interfaceLogger {
funlog(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:
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
*/funarrivalNumberRange() = 0..nextInt(3, 10)
funarrivalGroupRange() = 0..nextInt(1, 4)
// private val busStopRepository = ResourceBusStopRepository() // DELETE 1/**
* Implementation for the BusArrivalRepository which returns random values
*/classRandomBusArrivalRepositoryconstructor(
privateval busStopRepository: BusStopRepository, // 2privateval logger: Logger // 3
) : BusArrivalRepository {
overridesuspendfunfindBusArrival(busStopId: String): List<BusArrivalGroup> {
logger.log("Invoking findBusArrival for id: $busStopId on $this") // 4val 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:
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 rbzewghoh text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.