Introduction
Advantages of using Ktor Client:
Cross-platform support: Unlike Retrofit, which is Java-based and has different implementations for Android and iOS, Ktor is built on Kotlin Multiplatform Mobile (KMM). This means you can create both iOS and Android applications with Kotlin and share a significant portion of Kotlin code for both platforms. Ktor is designed for various platforms, such as Android, Native (iOS and desktop), JVM, and JavaScript.
Asynchronous and Coroutine support: Ktor is an asynchronous HTTP client that is entirely built on coroutines, enabling asynchronous programming with minimal boilerplate code.
Flexible and customizable: Ktor provides a flexible and easy-to-use API for making HTTP requests. You can easily customize requests by adding headers, query parameters, or other options.
Serialization and Logging support: Ktor integrates with kotlinx.serialization for serializing and deserializing JSON data, and it has a logging feature that logs all requests and responses, which helps in debugging the application.
Setup new Compose Project
After creating the project, open AndroidManifest.xml
and add internet permission as shown below:
<uses-permission android:name="android.permission.INTERNET"/>
Internet permission is required to make HTTP requests to the API.
Add dependency
The main client functionality is available in the ktor-client-core
artifact.
implementation "io.ktor:ktor-client-core:$ktor_version"
We need to use ContentNegotiation
for Serializing/deserializing sending request and receiving response JSON. To use ContentNegotiation
, we need to include the ktor-client-content-negotiation
artifact in the build script:
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
To serialize/deserialize JSON data, you can choose one of the following libraries: kotlinx.serialization
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
Logging
implementation "io.ktor:ktor-client-logging:$ktor_version"
Android
engine targets Android and can be configured in the following way:
implementation "io.ktor:ktor-client-android:$ktor_version"
Finally look like this:
def ktor_version = '2.3.0'
//Ktor Dependencies
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-logging:$ktor_version"
You can replace $ktor_version
with the required Ktor version, for example, 2.3.0
.
In project build.gradle(Module :app)
inside plugin add
plugins {
/*
other plugins...
*/
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
}
otherwise it gives you io.ktor.serialization.JsonConvertException: Illegal input
Error.
or Add in the gradle.app the next line in the plugins block : id 'kotlinx-serialization'
plugins {
id 'kotlinx-serialization'
}
Get the Data from API endpoints
For Sending and Receiving we need to create a data class Create a Models
package. Create a new Kotlin data class inside this package and name it Post.kt
to receive and send data, you need to have a data class, for example:
data class Post (
val id:Int,
val title:String,
val body: String,
val userId:String
)
Make sure that this class has the @Serializable
annotation, otherwise it throws kotlinx.serialization
error:
@Serializable
data class Post (
val id:Int,
val title:String,
val body: String,
val userId:String
)
Now, Setup our API routes refer from JSONPlaceholder - Guide but we using only these 4 routes:
Create a new package Network
for all our API calls. And we’ll create a new Kotlin Object file named ApiRoutes.kt
.
object ApiRoutes {
private const val BASE_URL:String = "https://jsonplaceholder.typicode.com"
var BLOG_POST = "$BASE_URL/posts"
}
After creating ApiRoutes
, We create a new Kotlin object
name as ApiClient.kt
for our HttpClient and a variable client
.
object ApiClient {
//body
}
and let's install all required plugins. Final, object
looks like,
package com.rhytham.ktorclient.Network
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.DefaultRequest
import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logging
import io.ktor.client.request.accept
import io.ktor.client.request.header
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
object ApiClient {
//Configure the HttpCLient
@OptIn(ExperimentalSerializationApi::class)
var client = HttpClient(Android) {
// For Logging
install(Logging) {
level = LogLevel.ALL
}
// Timeout plugin
install(HttpTimeout) {
requestTimeoutMillis = 15000L
connectTimeoutMillis = 15000L
socketTimeoutMillis = 15000L
}
// JSON Response properties
install(ContentNegotiation) {
json(
Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
explicitNulls = false
}
)
}
// Default request for POST, PUT, DELETE,etc...
install(DefaultRequest) {
header(HttpHeaders.ContentType, ContentType.Application.Json)
//add this accept() for accept Json Body or Raw Json as Request Body
accept(ContentType.Application.Json)
}
}
}
Create a repository ApiRepository,kt
to provide us with the required data from the Api endpoint.
package com.rhytham.ktorclient.Network
import com.rhytham.ktorclient.Model.Post
import com.rhytham.ktorclient.Network.ApiClient.client
import io.ktor.client.call.body
import io.ktor.client.request.delete
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.put
import io.ktor.client.request.setBody
class ApiRepository {
suspend fun getAllPosts(): List<Post> = client.get(ApiRoutes.BLOG_POST).body()
suspend fun createNewPost(newPost: Post): Post = client.post(ApiRoutes.BLOG_POST){
setBody(newPost)
}.body()
suspend fun updatePost(id:Int, post: Post):Post = client.put(ApiRoutes.BLOG_POST+"/$id"){
setBody(post)
}.body()
suspend fun deletePost(id:Int) = client.delete("${ApiRoutes.BLOG_POST}/$id")
}
Jetpack UI integration
Card Compose
Create a Card
Compose to show the response from the API endpoints.
@Composable
fun CodeCard(jsonStr: String) {
val scrollState = rememberScrollState()
Card(modifier = Modifier
.fillMaxWidth()
.verticalScroll(
state = scrollState,
enabled = true
)
.padding(all = 8.dp),
) {
Text(
modifier = Modifier.padding(start = 12.dp, top=12.dp),
text = "response: ",
style = MaterialTheme.typography.labelSmall,
fontFamily = FontFamily.Monospace
)
Text(
modifier = Modifier.padding(all = 12.dp),
text = jsonStr,
fontFamily = FontFamily.Monospace
)
}
}
Looks like:
Custom FilterGroup
Create a Custom Filter Group.
Get code from : Custom FilterChip Group using Jetpack Compose in Android
HomeScreen UI
Right click and create a new package Screen
inside the Screen
package create a kotlin file HomeScreen.kt
.
Define the
HomeScreen
function with the@Composable
annotation.Within the
HomeScreen
function, create aColumn
composable.
@Composable
fun HomeScreen() {
}
- Define a list of
chipsList
containing strings representing different HTTP request types and userememberCoroutineScope
to create a coroutine scope calledscope
.
val chipsList = listOf("/POST", "/GET", "/DELETE", "/PUT")
// Code
val scope = rememberCoroutineScope()
Create a
mutableStateOf
variable calledjsonRepose
that initially holds an empty string.Create an instance of an
ApiRepository
class.
val apiRepo = ApiRepository()
- Create a
mutableStateOf
variable calledshowLoading
that initially holdsfalse
. Define aPost
object with some sample data.
var showLoading by remember { mutableStateOf(false) }
Display the
headline
variable as aText
composable element .Use the
FilterChipGroup
composable element to display thechipsList
and handle the selection of different HTTP request types. When a chip is selected, update theheadLine
variable and reset thejsonRepose
variable to an empty string.
FilterChipGroup(items = chipsList,
onSelectedChanged = { selectedIndex:Int ->
headLine = chipsList[selectedIndex]
jsonRepose =""
})
- Display a
Button
composable element that sends the HTTP request when clicked. When the button is clicked, set theshowLoading
variable totrue
. Inside a coroutine scope, use awhen
expression to check the value ofheadLine
and call the appropriate method on theApiRepository
class to make the HTTP request. Set thejsonRepose
variable to the response of the HTTP request. Finally, set theshowLoading
variable back tofalse
.
Button(modifier = Modifier
.align(alignment = Alignment.CenterHorizontally)
.width(200.dp),
onClick = {
showLoading = true
scope.launch {
when(headLine){
"/POST" -> {
jsonRepose = apiRepo.createNewPost(post).toString()
} "/GET" -> {
jsonRepose = apiRepo.getAllPosts().toString()
} "/PUT" -> {
// Use PUT request to Update data
jsonRepose = apiRepo.updatePost(
id=1,
post = post ).toString()
} "/DELETE" -> {
jsonRepose = apiRepo.deletePost( id = 2).status.toString()
} } showLoading = !showLoading
}
}) {
Text(text = "Send")
}
- If
showLoading
istrue
, display aLinearProgressIndicator
composable element.
if(showLoading) {
LinearProgressIndicator( modifier = Modifier.fillMaxWidth())
}
- Display a custom
CodeCard
composable element that displays the JSON response in a formatted way.
CodeCard(jsonStr = jsonRepose)
That's it! We can now use this HomeScreen
function as a composable element in our app to allow users to make HTTP requests and display the response.
Get the Source Code from GitHub: KtorClientExample
Conclusion
Ktor is a framework to easily build connected applications – web applications, HTTP services, and mobile and browser applications. Modern connected applications need to be asynchronous to provide the best experience to users, and Kotlin Coroutines provide awesome facilities to do it easily and straightforwardly. The goal of Ktor is to provide an end-to-end multiplatform framework for connected applications. It enables asynchronous programming with minimal boilerplate code.
In this tutorial, we have learned how we can use the Ktor client and perform HTTP requests. We have used the response and displayed the data using Jetpack Compose.