This episode applies Kotlin to Android development: setting up an Android project with the Gradle Kotlin DSL, Android-specific features like extension functions and ViewBinding, ViewModel and LiveData alongside coroutines, and the basics of Jetpack Compose that write UI declaratively.

Kotlin and Android have gone hand in hand since 2017. Episode 10 takes you into the world of Android: project setup with Kotlin, the features that make Android development comfortable, the ViewModel and LiveData architecture, and Jetpack Compose, which writes UI declaratively with the Kotlin DSL.
Android is Kotlin's most massive use case, so mastering it opens the door to a vast ecosystem and many opportunities. Even so, the principles you learn here — lifecycle, coroutines, and architecture — also apply to the backend in episode 11.
By the end of this episode you'll run your first Android app that displays data asynchronously using modern patterns.
Modern Android projects use Gradle with the Kotlin DSL. The core configuration lives in the build.gradle.kts file:
plugins {
id("com.android.application") version "8.5.0" apply false
id("org.jetbrains.kotlin.android") version "2.0.0" apply false
}
android {
namespace = "id.devnull.myapp"
compileSdk = 35
defaultConfig {
applicationId = "id.devnull.myapp"
minSdk = 24
targetSdk = 35
}
kotlinOptions {
jvmTarget = "17"
}
}namespace determines the application package, compileSdk points to the SDK API level, and jvmTarget aligns the bytecode with the JDK. An Android project is built with the command ./gradlew assembleDebug, which produces a debug APK.
An Android project is organized in the app/src/main directory for main code, app/src/androidTest for instrumentation, and app/src/test for local unit tests. The key layout: MainActivity.kt as the entry point and AndroidManifest.xml, which declares the application components.
Extension functions make accessing views and parsing intents more concise. ViewBinding replaces error-prone findViewById with type-safe binding:
fun Context.showToast(pesan: String) {
Toast.makeText(this, pesan, Toast.LENGTH_SHORT).show()
}With the extension function above, calling showToast("Tersimpan") becomes available on every Context in the app. The Android ecosystem uses extension patterns like this very broadly — from dp-to-pixel conversion to accessing intent extras.
A ViewModel holds UI data that survives configuration changes like screen rotation. LiveData is an observable, lifecycle-aware data holder:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.MutableLiveData
class MainViewModel : ViewModel() {
private val _jumlah = MutableLiveData(0)
val jumlah: LiveData<Int> = _jumlah
fun tambah() {
_jumlah.value = (_jumlah.value ?: 0) + 1
}
}MutableLiveData is for writing inside the ViewModel, and the read-only LiveData is for the UI to use. The _jumlah.value access uses the elvis operator from episode 6 for a null initial value.
Data calls in Android use coroutines with viewModelScope — an automatic scope that is cancelled when the ViewModel is cleared:
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class DataViewModel : ViewModel() {
fun muatData() {
viewModelScope.launch {
val data = withContext(Dispatchers.IO) { repo.ambil() }
tampilkan(data)
}
}
}viewModelScope.launch runs a coroutine that is automatically cancelled when the ViewModel is destroyed — no more leaking background work. withContext(Dispatchers.IO) moves the I/O operation to the right thread.
Jetpack Compose is the modern UI toolkit that writes interfaces with Kotlin functions instead of XML layouts. The interface is expressed as a @Composable function:
import androidx.compose.material3.*
@Composable
fun LayarSapaan(nama: String) {
Text(text = "Halo, $nama!", style = MaterialTheme.typography.titleLarge)
}The LayarSapaan function is invoked in the UI hierarchy and renders text. State changes trigger automatic recomposition — you no longer write findViewById and manual updates.
Compose tracks state with remember and mutableStateOf:
import androidx.compose.runtime.*
@Composable
fun Counter() {
var jumlah by remember { mutableStateOf(0) }
Button(onClick = { jumlah++ }) {
Text("Klik: $jumlah")
}
}When jumlah changes, the composable function runs again and the UI updates. This declarative model eliminates the entire class of state-and-view synchronization bugs common in classic Android.
The app is built and run with Gradle and Android Studio tooling:
./gradlew assembleDebug
./gradlew installDebug
adb logcat --pid=$(adb shell pidof -s id.devnull.myapp)./gradlew installDebug installs the APK to the connected emulator or device, and adb logcat shows app logs at runtime. Both are part of an Android developer's daily workflow.
Episode 10 applied Kotlin to Android: project setup with the Gradle Kotlin DSL, extension functions and ViewBinding, the ViewModel and LiveData architecture with viewModelScope, and Jetpack Compose for declarative UI written with the Kotlin DSL.
The key takeaways:
@Composable functions and state../gradlew assembleDebug and installDebug are the basic workflow.In episode 11 we'll discuss Kotlin for backend development — building a backend with Ktor, Spring Boot, or Micronaut, routing and request handling, serialization, dependency injection and configuration, and writing a REST API with middleware.