Chapters

Hide chapters

Dagger by Tutorials

First Edition · Android 11 · Kotlin 1.4 · AS 4.1

A. Appendix A: The Busso Server
Written by Massimo Carli

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
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
Figure 20.2 — BussoServer’s Project Structure

Now, open Application.kt in the main package and look at its content:

// 1
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() { // 2
  // 3
  install(Locations)
  // 4
  install(ContentNegotiation) {
    gson {
      setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
    }
  }
  // 5
  routing {
    get("/") { // 6
      call.respondText("I'm working!!", contentType = ContentType.Text.Plain)
    }
    // Features // 7
    findBusStop()
    findBusArrivals()
    myLocation()
    weather()
  }
}

This simple code is quite standard for Ktor. Here:

  1. You define main() for the app by instantiating the engine that implements all the routing logic for the server. Here, you’re using Netty.
  2. The server engine needs some configuration that you put into a module. You usually provide this information using a module() extension function.
  3. 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.
  4. You use the ContentNegotiation feature, which allows you to manage JSON as the transport protocol. In this case, you use Gson as the parser.
  5. The routing is the logic that maps a specific URI pattern to some logic. You define routings in a routing block.
  6. 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.
  7. 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
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
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:

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
      )
    )
  }
}

The structure of this code is similar to that of the other endpoints. Here, you:

  1. Define FIND_BUS_STOP with the path of the URL you invoke to access the endpoint. You pass the latitude and longitude in the input with the lat and lng query parameters.

  2. Create ResourceBusStopRepository, which is the object you need to query the bus stop for a specific location. It’s an implementation of the BusStopRepository interface that simply reads the result from a JSON file in the resources folder. At the moment, the query always returns the same result, but that doesn’t matter for your purposes. :]

  3. Use @Location to define FindBusStopRequest to encapsulate the input for the query.

  4. Define findBusStop(). This is what you invoke in Application.kt to define the endpoint.

  5. When you receive a request, the input parameters are encapsulated into FindBusStopRequest, which you receive as an implicit parameter for get().

  6. Invoke findBusStopByLocation() on busStopRepository to get the result you send back to the client using respond() on call.

Other than the specific logic, something might not look right in this code: In 2, you create an instance of ResourceBusStopRepository. This is a composition relationship, which you’ve learned is something to avoid. Even worse, you have the same relationship in FindBusArrival.kt.

Check that out by opening FindBusArrival.kt to find the following:

const val FIND_BUS_ARRIVALS = "$API_VERSION/findBusArrivals/{stopId}"
private val busArrivalRepository = RandomBusArrivalRepository()
private val busStopRepository = ResourceBusStopRepository()
// ...

How can you improve this? A very simple option is to use Koin.

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.

As mentioned, an in-depth exploration of Koin is outside the scope of this book. In this chapter, you’ll just use it to fix the repository dependency. To do this, you need to:

  1. Install the dependencies for Koin.
  2. Look at the repository implementations you want to inject.
  3. Create a module with the objects you want to inject.
  4. Initialize Koin for Ktor.
  5. Inject the repository implementation into FindBusStop and FindBusArrival.

This is a simple example, but it gives you an idea about how to use Koin and how it differs from Dagger and Hilt.

Installing Koin dependencies

Open build.gradle in Figure 20.5:

Figure 20.5 — Gradle file for BussoServer
Figure 20.5 — Gradle file for BussoServer

Inside, add the following definition:

// ...

dependencies {
  // ...

  implementation "org.koin:koin-core:$koin_version" // 1
  implementation "org.koin:koin-ktor:$koin_version" // 2

  testImplementation "org.koin:koin-test:$koin_version" // 3

  // ...
}
// ...

Here, you just install the:

  1. Core Koin library.
  2. Koin library with some utilities for Ktor.
  3. Koin testing library.

koin_version is already available in gradle.properties, along with other version variables. It’s important to note that Koin doesn’t need an annotation processor because it doesn’t generate any code.

After you sync the BussoServer project with the Gradle file you just updated, you’re ready to use Koin.

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:

Figure 20.6 — Repositories dependency diagram
Figure 20.6 — Repositories dependency diagram

In this dependency diagram, note that:

  1. There are two different repository abstractions: BusStopRepository and BusArrivalRepository.
  2. ResourcesBusStopRepository is the implementation of BusStopRepository. RandomBusArrivalRepository is the implementation of BusArrivalRepository.
  3. FindBusStop depends on BusStopRepository.
  4. FindBusArrival depends on BusStopRepository and BusArrivalRepository.

It’s important to note that, at this point, the relationships at points 3 and 4 are compositions. Your goal is to use dependency injection to make those dependencies loosely coupled.

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.

To see this for yourself, create a new package named di and create a new file named RepositoryModule.kt in it with the following code:

val repositoryModule = module { // 1
  single<BusStopRepository> { ResourceBusStopRepository() } // 2
  single<BusArrivalRepository> { RandomBusArrivalRepository() } // 3
}

This code is very simple. It shows how to:

  1. Define a module for the repositories in BussoServer by passing a block to the module where you define the bindings.
  2. Create a binding between BusStopRepository and ResourceBusStopRepository. Using single, you tell Koin that you only have a single instance of ResourceBusStopRepository bound to the BusStopRepository type.
  3. Do the same for the BusArrivalRepository. Every time you inject an object of type BusArrivalRepository, you’ll use the instance of RandomBusArrivalRepository you created here.

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:

@KtorExperimentalLocationsAPI
@Suppress("unused") // Referenced in application.conf
fun Application.module() {

  install(org.koin.ktor.ext.Koin) { // 1
    modules(repositoryModule) // 2
  }
  // ...
}
// ...

The code is straightforward. Here, you:

  1. Use install to register the Koin feature with Ktor, just as you did for the Locations and ContentNegotiation features. It’s important to use org.koin.ktor.ext as the Koin class’ package.
  2. Declare which modules to use. In your case, you use modules(), passing the reference to the repositoryModule you defined above.

Now that the implementations of the repositories are in BussoServer’s dependency graph, you just need to inject them when needed.

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 ->
    // ...
  }
}

In this code, you need to:

  1. Delete the initialization of the busStopRepository property with the instance of ResourceBusStopRepository that you now need to inject.
  2. Use inject() to initialize the busStopRepository local variable of type FindBusStopRequest.

In this way, you inject the instance of ResourceBusStopRepository, which you defined in the module in repositoryModule, into FindBusStop.

Now, open FindBusArrivals.kt in the same apis package and add the following code:

const val FIND_BUS_ARRIVALS = "$API_VERSION/findBusArrivals/{stopId}"
// private val busArrivalRepository = RandomBusArrivalRepository() // DELETE 1
// private val busStopRepository = ResourceBusStopRepository() // DELETE 1
// ...
@KtorExperimentalLocationsAPI
fun Route.findBusArrivals() {

  val busStopRepository: BusStopRepository by inject()  // 2
  val busArrivalRepository: BusArrivalRepository by inject() // 2

  get<FindBusArrivalsRequest> { busStopInput ->
    // ..
  }
}

In this code, you do the same for RandomBusArrivalRepository. Here, you:

  1. Delete the initialization of busArrivalRepository and busStopRepository with instances of RandomBusArrivalRepository and ResourceBusStopRepository, respectively.
  2. Use inject() to inject the instances from repositoryModule, just as you did for FindBusStop before.

Finally, run BussoServer to see that everything works as expected. To verify this, open the browser and access a URL like http://localhost:8080/api/v1/findBusStop/1.0/2.0.

You’ll get what’s shown in figure 20.7:

Figure 20.7 — Verify BussoServer is working
Figure 20.7 — Verify BussoServer is working

In this case, injecting the repositories is simple because ResourceBusStopRepository and RandomBusArrivalRepository don’t have other dependencies. Next, you’ll see a slightly more complicated example.

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.

Suppose you now want to add a simple logger to check that the instances you’re using in the app are singletons or, using Koin language, are single. To do this, you just need to:

  1. Add a new Logger abstraction with a simple implementation.
  2. Create a module for the Logger.
  3. Install the module for the Logger in Ktor.
  4. Add the dependency to the Logger in ResourceBusStopRepository and RandomBusArrivalRepository.
  5. Provide dependencies in modules.

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)
}

Now, you just need a very simple implementation. So in the same package, create a new file named StdLoggerImpl.kt and add the following code:

class StdLoggerImpl : Logger {
  override fun log(msg: String) {
    println(msg)
  }
}

This implementation uses the built-in function print() to write the log message to the standard output.

As you learned, you now need a module.

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:

val loggerModule = module { // 1
  factory<Logger> { StdLoggerImpl() } // 2
}

In this case, you:

  1. Define loggerModule as a Koin module in the same way you did for the repositories.
  2. Used factory instead of single to define the binding of the Logger type to an instance of StdLoggerImpl.

You could have used single again but you’re using factory to show something different. In this case, you’ll get a different instance of Logger every time you inject one.

Now, you need to install this module in Ktor.

Installing the module for the Logger in Ktor

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
  }
  // ...
}

Here, you simply install loggerModule using modules(), as you did for repositoryModule earlier.

Great! Now, BussoServer knows that there’s a Logger somewhere — but it doesn’t know how to use it or which classes need it.

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.

Open ResourceBusStopRepository.kt in repository.impl and apply the following change:

class ResourceBusStopRepository constructor(
  private val logger: Logger // 1
) : BusStopRepository {

  private val model: BusStopData

  init {
    logger.log("Initializing ResourceBusStopRepository: $this") // 2
    val jsonAsText = this::class.java.getResource(BUS_STOP_RESOURCE_PATH).readText()
    model = Gson().fromJson(jsonAsText, BusStopData::class.java).apply {
      items.forEach { butStop ->
        this@apply.stopMap[butStop.id] = butStop
      }
    }
  }

  override suspend fun findBusStopByLocation(
    latitude: Float,
    longitude: Float,
    radius: Int
  ): List<BusStop> {
    logger.log("findBusStopByLocation on $this with lat:$latitude lon: $longitude") // 3
    return mutableListOf<BusStop>().apply {
      (2..10).forEach {
        add(model.items[it])
      }
    }.sortedBy { busStop -> busStop.distance }
  }


  override suspend fun findBusStopById(budStopId: String): BusStop? =
    model.stopMap[budStopId]
}
// ...

In this code, you:

  1. Create the dependency on Logger by adding a parameter in the primary constructor.
  2. Use logger when you initialize ResourceBusStopRepository.
  3. Log a message every time you invoke findBusStopByLocation.

In all the logs, you also print the specific instance of ResourceBusStopRepository you’re using. You’ll use this to prove that ResourceBusStopRepository is actually a singleton.

Now, you need to do the same for the other repository. Open RandomBusArrivalRepository.kt and apply similar changes, like this:

/**
 * 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()
          )
        )
      }
    }
  }

}

In this code, you also see something you didn’t know before. RandomBusArrivalRepository actually needs a BusStopRepository — so it depends on it. That’s because you:

  1. Delete the initialization of busStopRepository with a new instance of ResourceBusStopRepository, which must be injected.
  2. Define the dependency on BusStopRepository by adding a parameter of the same type to its primary constructor.
  3. Do the same for Logger.
  4. Use the logger to print a message every time you use RandomBusArrivalRepository.

Build now and you’ll get an error. You just added dependencies to ResourceBusStopRepository and RandomBusArrivalRepository, but Koin doesn’t know who’s providing those dependencies. You need to fix RepositoryModule.

Providing dependencies in modules

To resolve the dependencies, open RepositoryModule.kt in di and apply the following changes:

val repositoryModule = module {
  single<BusStopRepository> { ResourceBusStopRepository(get()) } // 1
  single<BusArrivalRepository> { RandomBusArrivalRepository(get(), get()) } // 2
}

All you need to do is add get() every time you need to resolve a dependency. In this code, you use:

  1. get() to resolve the dependency from ResourceBusStopRepository and Logger it requires as primary constructor parameter.
  2. get() twice to resolve the dependency between RandomBusArrivalRepository and the two primary constructor parameters. Koin is smart enough to understand the parameters’ types and to check if there’s a module that provides a binding for them.

Now, build and run, checking that everything works as expected, as you did in Figure 20.7.

More interesting is to prove that the instance of the repositories is always the same at each request. Check in Logcat and you’ll see something like this:

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

As you see, ResourceBusStopRepository is a singleton.

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.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.