Best Website for Jetpack Compose App Development

Android Jetpack Compose

Stay ahead with the latest tools, trends, and best practices in Android Jetpack Compose App development

How to Use Ktor in a Jetpack Compose App

How to Use Ktor in a Jetpack Compose App - Responsive Blogger Template
How to Use Ktor in a Jetpack Compose App

In the world of modern Android development, Jetpack Compose is quickly becoming the go-to UI toolkit for building beautiful and reactive interfaces using Kotlin. But once your UI is ready, the next big question is: how do you fetch data from APIs?

Most Android developers default to Retrofit, but there’s another powerful, Kotlin-native alternative: Ktor.

How to Use Ktor in a Jetpack Compose App

In this article, we’ll explore:
  • What is Ktor?
  • Why it fits perfectly with Jetpack Compose
  • How it compares to Retrofit
  • How to use Ktor in a real-world Compose project

πŸš€ What is Ktor?

Designed with modern Kotlin principles in mind, Ktor offers a sleek and modular way to handle HTTP in your app — and yes, it comes from the same brilliant minds at JetBrains who brought us Kotlin.
  • HTTP clients for apps (like Android, iOS)
  • HTTP servers for backends (like APIs)
Unlike some traditional Android networking libraries, Ktor is built entirely in Kotlin. That means fewer compatibility issues, less boilerplate, and smooth coroutine integration.

πŸ€”Benefits of Using Ktor for Network Calls in Jetpack Compose Applications

If you're building a Jetpack Compose app, you already value Kotlin's clean syntax and modern patterns. Here's why Ktor fits in naturally:

✅ 1. Kotlin-First Approach

Retrofit relies on annotations and reflection — concepts borrowed from Java. Ktor, on the other hand, is written purely in Kotlin and offers a more idiomatic style of coding.

✅ 2. Native Coroutine Support

Ktor works seamlessly with suspend functions, making it perfect for ViewModel logic in Compose apps. No adapters, no RxJava — just clean suspend fun calls.

✅ 3. Lightweight and Modular

Ktor doesn’t come bundled with unnecessary components. You pick exactly what you need: JSON, logging, timeout handling, etc. That keeps your APK smaller and more efficient.

✅ 4. Multiplatform Ready

Planning to reuse your logic in iOS or desktop apps using Kotlin Multiplatform? Ktor is already compatible across platforms.

πŸ” Ktor vs Retrofit – A Quick Comparison

FeatureKtorRetrofit
Language StylePure KotlinJava-style annotations
Coroutine Support✅ Native✅ Needs Coroutine Adapter
Multiplatform Support✅ Yes❌ Android-only
Ease of UseModerate (some learning)Easy (beginner-friendly)
CustomizationHigh (flexible pipeline)Medium

πŸ› ️ How to Use Ktor in a Jetpack Compose App

Let’s say you’re building a dictionary app. Here's a simple example using Ktor to fetch word definitions. We’ll build a mini Dictionary App using Free Dictionary API where:
  • User enters a word
  • Ktor fetches meaning from the API
  • Result is shown in a clean Jetpack Compose UI

✅ Tech Stack

  • Jetpack Compose
  • Ktor Client
  • ViewModel + StateFlow
  • Kotlinx Serialization
  • Material 3 Theme

πŸ“¦ Step-by-Step Project Code

Note: Internet Permission is Required

✅ How to Add Internet Permission

Open AndroidManifest.xml (in app/src/main/) and add this line above the <application> tag:
<uses-permission android:name="android.permission.INTERNET"/>

🧱 1. build.gradle dependencies

id("org.jetbrains.kotlin.plugin.serialization") // πŸ‘ˆ Add this line  
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.9.1")
// Ktor
implementation("io.ktor:ktor-client-core:2.3.2")
implementation("io.ktor:ktor-client-cio:2.3.2")
implementation("io.ktor:ktor-client-content-negotiation:2.3.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.2")

// Serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
build.gradle dependencies(Module:app)
build.gradle dependencies(Module:app)
Enable Kotlin serialization in build.gradle.kts

Add in your build.gradle.kts (Project):
kotlin("plugin.serialization") version "1.9.0"
build.gradle dependencies Project

🧩 2. Data Model (WordResponse.kt)

package com.example.ktordictionaryapp

import kotlinx.serialization.Serializable

@Serializable
data class WordResponse(
val word: String,
val meanings: List<Meaning>
)
@Serializable
data class Meaning(
val partOfSpeech: String,
val definitions: List<Definition>
)
@Serializable
data class Definition(
val definition: String,
val example: String? = null
)

🌐 3. Ktor Client (DictionaryApi.kt)

package com.example.ktordictionaryapp

import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import io.ktor.client.call.body

object DictionaryApi {

private val client = HttpClient(CIO) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
prettyPrint = true
isLenient = true
})
}
}

suspend fun getWordMeaning(word: String): List<WordResponse> {
return client
.get("https://api.dictionaryapi.dev/api/v2/entries/en/$word")
.body() // Deserialize to List<WordResponse>
}
}

🧠 4. ViewModel (DictionaryViewModel.kt)

package com.example.ktordictionaryapp

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.ktordictionaryapp.DictionaryApi.getWordMeaning
import kotlinx.coroutines.launch

class DictionaryViewModel : ViewModel() {
var word by mutableStateOf("")
var isLoading by mutableStateOf(false)
var result by mutableStateOf<List<WordResponse>>(emptyList())
var error by mutableStateOf<String?>(null)

fun search() {
viewModelScope.launch {
isLoading = true
error = null
try {
result = getWordMeaning(word)
} catch (e: Exception) {
error = "Word not found or network error"
result = emptyList()
} finally {
isLoading = false
}
}
}
}

🎨 5. UI Screen (MainScreen.kt)

package com.example.ktordictionaryapp

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun DictionaryScreen(viewModel: DictionaryViewModel = viewModel()) {
val word = viewModel.word
val result = viewModel.result
val isLoading = viewModel.isLoading
val error = viewModel.error

Column(
modifier = Modifier.systemBarsPadding()
.fillMaxSize()
.padding(16.dp)
) {

OutlinedTextField(
value = word,
onValueChange = { viewModel.word = it },
label = { Text("Enter a word") },
modifier = Modifier.fillMaxWidth()
)

Spacer(modifier = Modifier.height(8.dp))

Button(
onClick = viewModel::search,
modifier = Modifier.run { align(Alignment.End) }
) {
Text("Search")
}

Spacer(modifier = Modifier.height(16.dp))

when {
isLoading -> CircularProgressIndicator()
error != null -> Text(error, color = Color.Red)
result.isNotEmpty() -> {
LazyColumn {
items(result) { wordResponse ->
Text(
text = wordResponse.word,
style = MaterialTheme.typography.titleLarge
)
wordResponse.meanings.forEach { meaning ->
Text(
text = "- ${meaning.partOfSpeech}",
style = MaterialTheme.typography.labelLarge
)
meaning.definitions.forEach {
Text("• ${it.definition}")
it.example?.let { ex -> Text("Example: $ex") }
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}
}
}

🏁 6. MainActivity.kt

package com.example.ktordictionaryapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.ktordictionaryapp.ui.theme.KtorDictionaryAppTheme

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KtorDictionaryAppTheme {
/*Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}*/

DictionaryScreen()
}
}
}
}

✅ Output

  • User types a word like "happy"
  • Clicks Search
  • Ktor makes HTTP GET call to API
  • Shows definitions and examples using clean Compose UI
Dictionary app with Ktor Screenshot 1
Dictionary app with Ktor Screenshot 2

🧠 Summary

  • This Compose app uses Ktor for HTTP in a clean way.
  • You get full coroutine support and Kotlin-style networking.
  • The UI is reactive and modern — no XML, no Retrofit, no boilerplate.

Special Message

Welcome to Coding Bihar