Introduction
In modern Android development, Jetpack Compose and Ktor make it easier to build user interfaces and interact with APIs, respectively. Jetpack Compose simplifies UI development with a declarative approach, while Ktor provides an efficient way to perform HTTP requests. In this article, we'll explore how to combine these two powerful tools to fetch data from an API using Ktor and display it in a Jetpack Compose UI. The example involves creating an Android application that fetches a list of users from a placeholder API and presents it in a scrollable list format.
The project consists of three main components: the KtorClient
to handle network requests, a function to fetch user data from an API, and the MainActivity
that sets up the Jetpack Compose UI to display the fetched data. Each component plays a crucial role in ensuring that the app communicates effectively with the API and presents the data in a user-friendly manner.
KtorClient Setup
The KtorClient.kt
file configures an instance of Ktor's HTTP client. It sets up the basic network configurations required for API calls, including JSON serialization, logging, and timeouts.
First, a custom Json
object is initialized, allowing the app to encode default values and ignore unknown keys from API responses. This is particularly useful when dealing with APIs that may return fields not explicitly modeled in your app. The HttpClient
is then initialized with several features:
- JsonFeature: This feature uses
KotlinxSerializer
to serialize and deserialize JSON data. - Logging: This feature logs HTTP requests and responses. Here, a custom logger writes log messages using Android's
Log
class, making it easy to track network activity in the Android logcat. - HttpTimeout: This feature sets a timeout for socket, request, and connection operations, ensuring that the app doesn't hang indefinitely if the server is slow or unresponsive. Finally, a default request configuration ensures that all outgoing requests expect and send JSON content.
API Request Function
The APICaller.kt
file contains the getUsers
function, which is responsible for fetching user data from the API. This function is declared suspend
to leverage Kotlin's coroutines for asynchronous network calls. Using the KtorClient
instance, it makes a GET request to "https://jsonplaceholder.typicode.com/todos," a public API that returns a list of user tasks.
The response is automatically deserialized into a list of User
objects, thanks to the JSON serialization feature of Ktor. The User
data class is annotated with @Serializable
, which allows Kotlin to automatically handle JSON serialization and deserialization for this model. The User
class contains four properties: userId
, id
, title
, and completed
, corresponding to the fields returned by the API.
MainActivity and Jetpack Compose UI
The MainActivity.kt
file defines the main entry point for the application. It uses Jetpack Compose to build a simple UI that displays a list of user tasks. The MainActivity
calls the MainContent
composable, which is responsible for laying out the user interface.
Within the MainContent
composable, a produceState
function is used to asynchronously fetch the user data by calling getUsers()
. The produceState
function ensures that the user data is fetched only once when the composable is first displayed. As the data is fetched, it updates the UI with the user tasks in a LazyColumn
, which is a performant way to display a large list of items in a scrollable view.
Each user task is represented by a Card
composable, which dynamically adjusts its background color based on whether the task is completed. Inside the card, the Row
composable arranges the content horizontally, including a circular colored box displaying the user's ID and a Text
composable showing the task title.
Gradle Setup
The project requires specific dependencies to enable Ktor's functionality. In the build.gradle
file for the project, the Kotlin serialization plugin is added, allowing the use of Kotlin's built-in serialization library. The app
module's build.gradle
includes several dependencies:
ktor-client-android
: This is the core Ktor client library for Android.ktor-client-serialization
: Enables JSON serialization using Ktor.ktor-client-logging-jvm
: Adds logging capabilities for network requests.
Additionally, the org.jetbrains.kotlinx:kotlinx-serialization-json
dependency is included to handle JSON serialization, making it easier to work with JSON data returned by APIs.
Summary
By combining Jetpack Compose and Ktor, Android developers can build modern, performant applications that interact with APIs seamlessly. In this example, we configured a Ktor client with essential features like JSON serialization and logging, and used it to fetch user data from an API. The Jetpack Compose UI efficiently displayed the fetched data using a LazyColumn
, offering a smooth user experience.
This approach highlights the power of Kotlin's coroutines and Jetpack Compose's declarative UI system, allowing for asynchronous operations to be handled elegantly and user interfaces to be updated dynamically based on real-time data. With this setup, developers can build responsive and network-enabled Android apps with minimal effort.
package com.cfsuman.composenetwork
import android.util.Log
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.features.logging.*
import io.ktor.client.request.*
import io.ktor.http.*
import kotlinx.serialization.json.Json
object KtorClient{
private val json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
}
val httpClient = HttpClient {
install(JsonFeature){
serializer = KotlinxSerializer(json)
}
install(Logging){
logger = object : Logger {
override fun log(message: String) {
Log.d("xapp-ktor", message )
}
}
level = LogLevel.ALL
}
install(HttpTimeout){
socketTimeoutMillis = 15_000
requestTimeoutMillis = 15_000
connectTimeoutMillis = 15_000
}
defaultRequest {
contentType(ContentType.Application.Json)
accept(ContentType.Application.Json)
}
}
}
package com.cfsuman.composenetwork
import io.ktor.client.request.*
import io.ktor.utils.io.core.*
import kotlinx.serialization.Serializable
suspend fun getUsers(): List<User> {
return KtorClient.httpClient.use {
it.get("https://jsonplaceholder.typicode.com/todos")
}
}
@Serializable
data class User(
val userId: Int,
val id: Int,
val title: String,
val completed: Boolean
)
package com.cfsuman.composenetwork
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.cfsuman.composenetwork.ui.theme.ComposeNetworkTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ComposeNetworkTheme {
MainContent()
}
}
}
}
@Composable
fun MainContent() {
Log.d("xapp-called","Compose")
val users = produceState(
initialValue = listOf<User>(),
producer = {
value = getUsers()
Log.d("xapp-called","Producer")
}
)
LazyColumn(
Modifier.fillMaxSize(),
contentPadding = PaddingValues(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
){
items(users.value){ user->
Card(
modifier = Modifier.fillMaxWidth(),
elevation = 2.dp,
backgroundColor = if (user.completed)
Color(0xFFB6F1B8) else Color.White
) {
Row(
Modifier.padding(12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
Box(
Modifier
.clip(CircleShape)
.background(Color(0xFF4CAF50))
.size(48.dp),
contentAlignment = Alignment.Center
) {
Text(text = "${user.id}")
}
Text(text = user.title)
}
}
}
}
}
buildscript {
dependencies {
classpath "org.jetbrains.kotlin:kotlin-serialization:1.6.10"
}
}
plugins {
id 'kotlinx-serialization'
}
dependencies {
implementation 'io.ktor:ktor-client-android:1.6.8'
implementation 'io.ktor:ktor-client-serialization:1.6.8'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0'
implementation 'io.ktor:ktor-client-logging-jvm:1.6.8'
}
- jetpack compose - How to hide status bar
- jetpack compose - How to hide navigation bar
- jetpack compose - How to hide system bars
- jetpack compose - Detect screen orientation change
- jetpack compose - Get text from url
- jetpack compose ktor - How to pass parameters
- jetpack compose - Kotlinx serialization ignore unknown keys
- jetpack compose - Kotlinx serialization alternative json names
- jetpack compose - Kotlinx serialization decode from string
- jetpack compose - Kotlinx serialization encode defaults
- jetpack compose - Kotlinx serialization build json array
- jetpack compose - Kotlinx serialization build json object
- jetpack compose - Flow current time
- jetpack compose - How to flow a list
- jetpack compose - IconButton from vector resource