Ktor In KMM
Introduction
While working on my personal Kotlin multiplatform project, I was looking for a multiplatform http client to help with putting my project together. I came across Ktor a http client built with Kotlin and coroutines that works on multiplatform projects.
Key Points
- Point 1: Ktor project setup
- Point 2: Http client configuration
- Point 3: Making API call
Section 1: Ktor project setup
Adding Dependencies
- We want to add the following dependencies
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-logging = {module = "io.ktor:ktor-client-logging", version.ref = "ktor"}
ktor-client-content-negotiation = {module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor"}
ktor-serialization-json = {module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor"}
kotlinxSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }- With those dependencies we want to start adding them to our gradle
- When we’re targeting
iOSwe use thedarwinengine dependency, when targetingandroidwe use theokhttpengine dependency, and forcommonMainsource set we will use thecorehttp client dependency. - We also need to add ktor JSON serialization plugin to be able to deserialize the JSON response into our Kotlin objects. We need to install the
ContentNegotiationplugin withKotlinxSerialization, and we also want to installLoggingso we need to use logging dependency.
- When we’re targeting
plugins {
alias(libs.plugins.kotlinxSerialization)
}
sourceSets {
iosMain.dependencies {
implementation(libs.ktor.client.darwin)
}
androidMain.dependencies {
implementation(libs.ktor.client.okhttp)
}
commonMain.dependencies {
//put your multiplatform dependencies here
implementation(libs.ktor.client.core)
implementation(libs.ktor.client.logging)
implementation(libs.ktor.client.content.negotiation)
implementation(libs.ktor.serialization.json)
}
}Section 2: Http client configuration
import io.ktor.client.HttpClient
import io.ktor.client.engine.HttpClientEngine
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
fun createHttpClient(client: HttpClientEngine): HttpClient {
return HttpClient(client) {
install(Logging){
logger = Logger.DEFAULT
level = LogLevel.ALL
}
install(ContentNegotiation){
json(
json = Json {
prettyPrint = true
isLenient = true
// if API returns JSON fields we don't have
// defined it will ignore it and will not crash our app ignoreUnknownKeys = true
},
contentType =
)
}
}
}Section 3: Making API call
private val httpClient: HttpClient
override suspend fun getRandomQuotes(): Result<Quote, NetworkError> {
val response = try {
httpClient.get(urlString = BASE_URL) {
headers {
append(
"X-Api-Key",
value = ""
)
}
} } catch (e: UnresolvedAddressException) {
return Result.Error(NetworkError.NO_INTERNET)
} catch (e: SerializationException) {
return Result.Error(NetworkError.SERIALIZATION)
}
return when (response.status.value) {
in 200..299 -> {
val quote = Result.Success(response.body<Quote>())
Result.Success(quote.data)
}
401 -> Result.Error(NetworkError.UNAUTHORIZED)
408 -> Result.Error(NetworkError.REQUEST_TIMEOUT)
409 -> Result.Error(NetworkError.CONFLICT)
413 -> Result.Error(NetworkError.PAYLOAD_TOO_LARGE)
in 500..599 -> Result.Error(NetworkError.SERVER_ERROR)
else -> Result.Error(NetworkError.UNKNOWN)
}
}Conclusion
- We’re now able to make API calls on many different platforms, and this allows us to create code that is reusable on many different platforms.