Mobile

Android Development dengan Kotlin

TOKEN

Panduan lengkap membangun aplikasi Android native dengan Kotlin — dari dasar bahasa, Android Studio, Activities, Fragments, RecyclerView, Room database, hingga pengenalan Jetpack Compose

1. Dasar Pemrograman Kotlin

Kotlin adalah bahasa pemrograman modern yang dikembangkan oleh JetBrains dan menjadi bahasa utama untuk pengembangan Android sejak Google mengumumkannya sebagai "first-class language" untuk Android pada tahun 2019. Kotlin 100% interoperable dengan Java, memiliki sintaks yang lebih ringkas, dan menghilangkan banyak boilerplate code.

Mengapa Kotlin untuk Android?

Keunggulan Penjelasan
Null SafetySistem tipe nullable/non-nullable yang mencegah NullPointerException di compile-time
Concise SyntaxLebih ringkas dari Java — data class, extension functions, lambda
CoroutinesAsync programming yang elegan tanpa callback hell
Java InteropBisa menggunakan semua library Java yang sudah ada
Google SupportDirekomendasikan Google, semua library Jetpack mendukung Kotlin-first
Smart CastsAutomatic type casting setelah null check

Variabel & Tipe Data

Kotlin
// ===== VARIABEL =====
val nama: String = "BeebaneLabs"   // Immutable (tidak bisa diubah)
var umur: Int = 25                  // Mutable (bisa diubah)

// Type inference — tipe di-inferensi otomatis
val kota = "Jakarta"               // String
val populasi = 10_000_000          // Int (underscore untuk readability)
val tinggi = 175.5                  // Double
val isActive = true                 // Boolean

// Nullable types
var alamat: String? = null          // Bisa null dengan ?
var namaLengkap: String = "Budi"    // Tidak bisa null

// Safe call operator ?. dan Elvis operator ?:
val panjang = alamat?.length ?: 0   // Jika null, hasilnya 0

// ===== TIPE DATA DASAR =====
val angka: Byte = 127               // 8-bit
val angka2: Short = 32767           // 16-bit
val angka3: Int = 2147483647        // 32-bit
val angka4: Long = 9_223_372_036_854_775_807  // 64-bit
val desimal: Float = 3.14f          // 32-bit
val desimal2: Double = 3.14159265   // 64-bit
val huruf: Char = 'A'               // Single character
val teks: String = "Hello Kotlin"   // String

Function & Lambda

Kotlin
// Function biasa
fun tambah(a: Int, b: Int): Int {
    return a + b
}

// Single-expression function
fun kali(a: Int, b: Int): Int = a * b

// Default parameters & named arguments
fun sapa(nama: String, pesan: String = "Halo"): String {
    return "$pesan, $nama!"
}
// sapa("Budi")              → "Halo, Budi!"
// sapa("Budi", pesan="Hey") → "Hey, Budi!"

// Extension function
fun String.kebalikan(): String = this.reversed()
// "Hello".kebalikan() → "olleH"

// Lambda
val kuadrat = { x: Int -> x * x }
val list = listOf(1, 2, 3, 4, 5)
val genap = list.filter { it % 2 == 0 }
val hasil = list.map { it * 2 }

// Higher-order function
fun operasi(a: Int, b: Int, op: (Int, Int) -> Int): Int {
    return op(a, b)
}
val hasilTambah = operasi(5, 3) { x, y -> x + y }

// Coroutines — async
suspend fun fetchData(): String {
    delay(1000)  // Non-blocking delay
    return "Data dari server"
}

Class & Data Class

Kotlin
// Class biasa
class Mahasiswa(val nama: String, var umur: Int) {
    var jurusan: String = "Teknik Informatika"

    init {
        println("Mahasiswa $nama dibuat")
    }

    fun perkenalkan() {
        println("Halo, saya $nama, umur $umur, jurusan $jurusan")
    }
}

// Data class — otomatis mendapat equals, hashCode, toString, copy
data class Produk(
    val id: Int,
    val nama: String,
    val harga: Int,
    val stok: Int = 0
)

val laptop = Produk(1, "ASUS ROG", 15000000, 10)
val laptopBaru = laptop.copy(harga = 13000000, stok = 5)
println(laptop)  // Produk(id=1, nama=ASUS ROG, harga=15000000, stok=10)

// Sealed class — untuk representasi tipe yang terbatas
sealed class Hasil<out T>
class Sukses<T>(val data: T) : Hasil<T>()
class Error(val pesan: String) : Hasil<Nothing>()
class Loading : Hasil<Nothing>()

fun handleHasil(hasil: Hasil<String>) {
    when (hasil) {
        is Sukses -> println("Data: ${hasil.data}")
        is Error -> println("Error: ${hasil.pesan}")
        is Loading -> println("Loading...")
    }
}

2. Setup Android Studio

Android Studio adalah IDE resmi untuk pengembangan Android, berdasarkan IntelliJ IDEA. Android Studio menyediakan semua tools yang dibutuhkan: editor kode, visual layout editor, emulator, debugger, dan profiler.

Langkah Instalasi

Setup
# 1. Download Android Studio dari https://developer.android.com/studio
#    Versi terbaru: Android Studio Ladybug (2024.2)

# 2. Instal Android Studio (wizard akan memandu)
#    - Instal Android SDK
#    - Instal Android Virtual Device (AVD)
#    - Instal SDK Build Tools

# 3. Setelah instalasi, buka SDK Manager:
#    File → Settings → Appearance → System Settings → Android SDK
#    - SDK Platforms: Android 14 (API 34) atau terbaru
#    - SDK Tools: Android SDK Build-Tools, CMake, NDK

# 4. Buat AVD (Android Virtual Device):
#    Tools → Device Manager → Create Device
#    - Pilih device (misal Pixel 8)
#    - Pilih system image (API 34)
#    - Finish

# 5. Buat proyek baru:
#    File → New → New Project
#    - Template: Empty Activity (Compose) atau Empty Views Activity
#    - Language: Kotlin
#    - Minimum SDK: API 24 (Android 7.0)
#    - Build system: Gradle (Kotlin DSL)

Struktur Proyek Android

File Structure
MyAndroidApp/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/com/example/myapp/
│   │   │   │   ├── MainActivity.kt        ← Entry point
│   │   │   │   ├── ui/                    ← UI components
│   │   │   │   │   ├── theme/
│   │   │   │   │   └── screens/
│   │   │   │   ├── data/                  ← Data layer
│   │   │   │   │   ├── local/             ← Room database
│   │   │   │   │   └── remote/            ← API service
│   │   │   │   └── model/                 ← Data models
│   │   │   ├── res/                       ← Resources
│   │   │   │   ├── layout/               ← XML layouts (Views)
│   │   │   │   ├── values/               ← strings, colors, themes
│   │   │   │   ├── drawable/             ← Gambar, shapes
│   │   │   │   └── mipmap/              ← App icons
│   │   │   └── AndroidManifest.xml       ← Konfigurasi app
│   │   └── test/                         ← Unit tests
│   └── build.gradle.kts                  ← Konfigurasi build app
├── gradle/
├── build.gradle.kts                      ← Konfigurasi build root
├── settings.gradle.kts
└── gradle.properties
💡 Gradle Kotlin DSL

Android Studio terbaru menggunakan build.gradle.kts (Kotlin DSL) sebagai pengganti build.gradle (Groovy). Kotlin DSL memberikan autocompletion yang lebih baik dan type-safety saat mengonfigurasi dependencies. Pastikan untuk selalu Sync Project setelah mengubah file Gradle.

3. Activities & Lifecycle

Activity adalah komponen fundamental Android yang merepresentasikan satu layar. Setiap Activity memiliki lifecycle yang dikelola oleh sistem Android — memahami lifecycle ini sangat penting untuk mengelola resource dengan benar.

Diagram: Activity Lifecycle
          onCreate()
              │
              ▼
          onStart()
              │
              ▼
         onResume() ◄──────────────┐
              │                    │
              ▼                    │
     [Activity Running]            │
              │                    │
              ▼                    │
         onPause()                 │
              │                    │
              ▼                    │
          onStop()                 │
              │         onRestart()│
              ▼                    │
         onDestroy()               │
                                   
  User kembali → onStart() ───────┘

Activity Dasar

Kotlin — MainActivity.kt
package com.example.myapp

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "MainActivity"
    }

    // View references
    private lateinit var tvGreeting: TextView
    private lateinit var btnNavigate: Button

    // ===== LIFECYCLE METHODS =====

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)  // Set layout XML
        Log.d(TAG, "onCreate")

        // Inisialisasi views
        tvGreeting = findViewById(R.id.tvGreeting)
        btnNavigate = findViewById(R.id.btnNavigate)

        // Set teks
        tvGreeting.text = "Selamat Datang di Aplikasi Android!"

        // Navigasi ke Activity lain
        btnNavigate.setOnClickListener {
            val intent = Intent(this, DetailActivity::class.java).apply {
                putExtra("ITEM_ID", 42)
                putExtra("ITEM_NAME", "Produk Kotlin")
            }
            startActivity(intent)
        }
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG, "onStart — Activity terlihat")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG, "onResume — Activity berinteraksi")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG, "onPause — Activity terhalang sebagian")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG, "onStop — Activity tidak terlihat")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG, "onDestroy — Activity dihancurkan")
    }
}

Menerima Data dari Activity Lain

Kotlin — DetailActivity.kt
package com.example.myapp

import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class DetailActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_detail)

        // Menerima data dari Intent
        val itemId = intent.getIntExtra("ITEM_ID", -1)
        val itemName = intent.getStringExtra("ITEM_NAME") ?: "Unknown"

        val tvDetail: TextView = findViewById(R.id.tvDetail)
        tvDetail.text = "ID: $itemId\nNama: $itemName"

        // Enable back button di action bar
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.title = itemName
    }

    override fun onSupportNavigateUp(): Boolean {
        finish()  // Kembali ke Activity sebelumnya
        return true
    }
}

XML Layout

XML — res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="24dp"
    android:background="#FAFAFA">

    <TextView
        android:id="@+id/tvGreeting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Halo, Android!"
        android:textSize="24sp"
        android:textStyle="bold"
        android:textColor="#333333"
        android:layout_marginBottom="16dp" />

    <EditText
        android:id="@+id/etInput"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Ketik sesuatu..."
        android:padding="12dp"
        android:background="@drawable/edit_text_bg"
        android:layout_marginBottom="16dp" />

    <Button
        android:id="@+id/btnNavigate"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Buka Detail"
        android:backgroundTint="#2196F3"
        android:textColor="#FFFFFF"
        android:padding="12dp" />

</LinearLayout>

4. Fragments & Multi-Screen

Fragment adalah komponen UI yang bisa digunakan kembali di dalam Activity. Fragments memungkinkan desain modular yang bisa beradaptasi dengan berbagai ukuran layar (phone vs tablet).

Kotlin — HomeFragment.kt
package com.example.myapp.ui

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.myapp.R

class HomeFragment : Fragment() {

    private var paramNama: String? = null

    companion object {
        fun newInstance(nama: String): HomeFragment {
            val fragment = HomeFragment()
            val args = Bundle().apply {
                putString("NAMA", nama)
            }
            fragment.arguments = args
            return fragment
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        paramNama = arguments?.getString("NAMA") ?: "Pengguna"
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val tvWelcome: TextView = view.findViewById(R.id.tvWelcome)
        val btnToProfile: Button = view.findViewById(R.id.btnToProfile)

        tvWelcome.text = "Selamat datang, $paramNama!"

        btnToProfile.setOnClickListener {
            parentFragmentManager.beginTransaction()
                .replace(R.id.fragmentContainer, ProfileFragment())
                .addToBackStack(null)
                .commit()
        }
    }
}

Bottom Navigation dengan Fragments

Kotlin — MainActivity dengan Bottom Nav
package com.example.myapp

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import com.google.android.material.bottomnavigation.BottomNavigationView

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val bottomNav: BottomNavigationView = findViewById(R.id.bottomNav)

        // Load fragment awal
        if (savedInstanceState == null) {
            loadFragment(HomeFragment())
        }

        bottomNav.setOnItemSelectedListener { item ->
            val fragment: Fragment = when (item.itemId) {
                R.id.nav_home -> HomeFragment()
                R.id.nav_search -> SearchFragment()
                R.id.nav_profile -> ProfileFragment()
                else -> HomeFragment()
            }
            loadFragment(fragment)
            true
        }
    }

    private fun loadFragment(fragment: Fragment) {
        supportFragmentManager.beginTransaction()
            .replace(R.id.fragmentContainer, fragment)
            .commit()
    }
}

5. RecyclerView & Adapters

RecyclerView adalah komponen untuk menampilkan daftar data yang panjang secara efisien. RecyclerView hanya me-render item yang terlihat di layar dan mendaur ulang view yang sudah tidak terlihat.

Data Model

Kotlin — Data Model
// Model data
data class Artikel(
    val id: Int,
    val judul: String,
    val deskripsi: String,
    val gambarUrl: String,
    val tanggal: String
)

Adapter & ViewHolder

Kotlin — ArtikelAdapter.kt
package com.example.myapp.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.myapp.R
import com.example.myapp.model.Artikel

class ArtikelAdapter(
    private val onItemClick: (Artikel) -> Unit
) : ListAdapter<Artikel, ArtikelAdapter.ArtikelViewHolder>(ArtikelDiffCallback()) {

    // ViewHolder — menyimpan referensi ke view
    class ArtikelViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvJudul: TextView = itemView.findViewById(R.id.tvJudul)
        val tvDeskripsi: TextView = itemView.findViewById(R.id.tvDeskripsi)
        val tvTanggal: TextView = itemView.findViewById(R.id.tvTanggal)
        val imgArtikel: ImageView = itemView.findViewById(R.id.imgArtikel)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArtikelViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_artikel, parent, false)
        return ArtikelViewHolder(view)
    }

    override fun onBindViewHolder(holder: ArtikelViewHolder, position: Int) {
        val artikel = getItem(position)

        holder.tvJudul.text = artikel.judul
        holder.tvDeskripsi.text = artikel.deskripsi
        holder.tvTanggal.text = artikel.tanggal

        // Load image dengan Glide/Coil
        // Glide.with(holder.itemView.context)
        //     .load(artikel.gambarUrl)
        //     .into(holder.imgArtikel)

        holder.itemView.setOnClickListener {
            onItemClick(artikel)
        }
    }
}

// DiffUtil — efisien membandingkan list lama vs baru
class ArtikelDiffCallback : DiffUtil.ItemCallback<Artikel>() {
    override fun areItemsTheSame(oldItem: Artikel, newItem: Artikel) =
        oldItem.id == newItem.id

    override fun areContentsTheSame(oldItem: Artikel, newItem: Artikel) =
        oldItem == newItem
}

Menggunakan RecyclerView di Fragment

Kotlin — ArtikelListFragment.kt
class ArtikelListFragment : Fragment() {

    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: ArtikelAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_artikel_list, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        recyclerView = view.findViewById(R.id.rvArtikel)

        adapter = ArtikelAdapter { artikel ->
            // Handle click — navigasi ke detail
            Toast.makeText(context, "Klik: ${artikel.judul}", Toast.LENGTH_SHORT).show()
        }

        recyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = this@ArtikelListFragment.adapter
            setHasFixedSize(true)
        }

        // Submit data
        val dataDummy = listOf(
            Artikel(1, "Belajar Kotlin", "Dasar-dasar Kotlin", "", "25 Jun 2026"),
            Artikel(2, "Android Studio", "Setup & konfigurasi", "", "24 Jun 2026"),
            Artikel(3, "Jetpack Compose", "UI modern Android", "", "23 Jun 2026"),
        )
        adapter.submitList(dataDummy)
    }
}

6. Room Database

Room adalah library dari Android Jetpack yang menyediakan abstraksi di atas SQLite. Room memberikan compile-time SQL verification, type-safe queries, dan integrasi yang baik dengan LiveData/Flow.

Setup Dependencies

Kotlin DSL — build.gradle.kts (app)
dependencies {
    // Room Database
    val roomVersion = "2.6.1"
    implementation("androidx.room:room-runtime:$roomVersion")
    implementation("androidx.room:room-ktx:$roomVersion")  // Coroutines support
    kapt("androidx.room:room-compiler:$roomVersion")        // Annotation processor

    // ViewModel & LiveData
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
}

Entity, DAO, Database

Kotlin — Room Components
package com.example.myapp.data

import androidx.lifecycle.LiveData
import androidx.room.*

// ===== ENTITY =====
@Entity(tableName = "catatan")
data class Catatan(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,

    @ColumnInfo(name = "judul")
    val judul: String,

    @ColumnInfo(name = "isi")
    val isi: String,

    @ColumnInfo(name = "tanggal")
    val tanggal: Long = System.currentTimeMillis(),

    @ColumnInfo(name = "favorit")
    val favorit: Boolean = false
)

// ===== DAO (Data Access Object) =====
@Dao
interface CatatanDao {

    @Query("SELECT * FROM catatan ORDER BY tanggal DESC")
    fun getAll(): LiveData<List<Catatan>>

    @Query("SELECT * FROM catatan WHERE id = :id")
    suspend fun getById(id: Int): Catatan?

    @Query("SELECT * FROM catatan WHERE judul LIKE '%' || :query || '%'")
    fun search(query: String): LiveData<List<Catatan>>

    @Query("SELECT * FROM catatan WHERE favorit = 1")
    fun getFavorites(): LiveData<List<Catatan>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(catatan: Catatan)

    @Update
    suspend fun update(catatan: Catatan)

    @Delete
    suspend fun delete(catatan: Catatan)

    @Query("DELETE FROM catatan")
    suspend fun deleteAll()
}

// ===== DATABASE =====
@Database(entities = [Catatan::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {

    abstract fun catatanDao(): CatatanDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

ViewModel dengan Room

Kotlin — CatatanViewModel.kt
package com.example.myapp.ui

import android.app.Application
import androidx.lifecycle.*
import com.example.myapp.data.AppDatabase
import com.example.myapp.data.Catatan
import kotlinx.coroutines.launch

class CatatanViewModel(application: Application) : AndroidViewModel(application) {

    private val dao = AppDatabase.getDatabase(application).catatanDao()

    // LiveData — otomatis update UI saat data berubah
    val semuaCatatan: LiveData<List<Catatan>> = dao.getAll()
    val favoritCatatan: LiveData<List<Catatan>> = dao.getFavorites()

    private val _searchQuery = MutableLiveData<String>("")
    val searchResults: LiveData<List<Catatan>> = _searchQuery.switchMap { query ->
        if (query.isBlank()) dao.getAll()
        else dao.search(query)
    }

    fun tambah(judul: String, isi: String) {
        viewModelScope.launch {
            dao.insert(Catatan(judul = judul, isi = isi))
        }
    }

    fun hapus(catatan: Catatan) {
        viewModelScope.launch {
            dao.delete(catatan)
        }
    }

    fun toggleFavorit(catatan: Catatan) {
        viewModelScope.launch {
            dao.update(catatan.copy(favorit = !catatan.favorit))
        }
    }

    fun search(query: String) {
        _searchQuery.value = query
    }
}

7. Pengenalan Jetpack Compose

Jetpack Compose adalah toolkit UI modern Android yang menggunakan pendekatan declarative — mirip Flutter dan React Native. Compose menggantikan XML layouts dengan kode Kotlin yang lebih fleksibel dan type-safe.

Compose vs XML Views

Aspek Jetpack Compose XML Views
PendekatanDeclarative (Kotlin DSL)Imperative (XML)
State ManagementBuilt-in (State, remember)Manual (setText, dll)
Preview@Preview annotationLayout Editor visual
ReusabilityComposable functionsCustom Views / Includes
PerformanceSmart recompositionManual optimization
Learning CurveMudah (jika tahu Kotlin)Sedang (XML + Java/Kotlin)

Composable Dasar

Kotlin — Jetpack Compose
package com.example.myapp.ui

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

// ===== COMPOSABLE FUNCTION =====
@Composable
fun GreetingScreen() {
    // State — otomatis recompose saat berubah
    var nama by remember { mutableStateOf("") }
    var sapaan by remember { mutableStateOf("") }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "Halo, Jetpack Compose!",
            style = MaterialTheme.typography.headlineMedium,
            color = MaterialTheme.colorScheme.primary
        )

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

        // Input field
        OutlinedTextField(
            value = nama,
            onValueChange = { nama = it },
            label = { Text("Nama Anda") },
            modifier = Modifier.fillMaxWidth()
        )

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

        // Button
        Button(
            onClick = { sapaan = "Halo, $nama! 👋" },
            modifier = Modifier.fillMaxWidth(),
            enabled = nama.isNotBlank()
        ) {
            Text("Sapa!")
        }

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

        // Hasil
        if (sapaan.isNotEmpty()) {
            Card(
                modifier = Modifier.fillMaxWidth(),
                colors = CardDefaults.cardColors(
                    containerColor = MaterialTheme.colorScheme.primaryContainer
                )
            ) {
                Text(
                    text = sapaan,
                    modifier = Modifier.padding(16.dp),
                    style = MaterialTheme.typography.bodyLarge
                )
            }
        }
    }
}

LazyColumn (List di Compose)

Kotlin — LazyColumn
// Data model
data class Item(val id: Int, val nama: String, val harga: Int)

@Composable
fun ItemListScreen() {
    val items = remember {
        listOf(
            Item(1, "Laptop ASUS", 12000000),
            Item(2, "Keyboard Mekanik", 850000),
            Item(3, "Monitor 27\"", 4500000),
            Item(4, "Mouse Logitech", 350000),
        )
    }

    LazyColumn(
        modifier = Modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(items) { item ->
            Card(
                modifier = Modifier.fillMaxWidth()
            ) {
                Row(
                    modifier = Modifier
                        .padding(16.dp)
                        .fillMaxWidth(),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Column {
                        Text(
                            text = item.nama,
                            style = MaterialTheme.typography.titleMedium
                        )
                        Text(
                            text = "Rp ${item.harga.toLocaleString()}",
                            color = MaterialTheme.colorScheme.primary,
                            style = MaterialTheme.typography.bodyMedium
                        )
                    }
                    Button(onClick = { /* Tambah ke keranjang */ }) {
                        Text("Beli")
                    }
                }
            }
        }
    }
}

// Entry point Activity
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    ItemListScreen()
                }
            }
        }
    }
}
💡 Kapan Mulai dengan Compose?

Untuk proyek baru, Google merekomendasikan langsung menggunakan Jetpack Compose. Compose adalah masa depan Android UI development — semua library Jetpack baru sudah Compose-first. Namun, masih banyak codebase lama yang menggunakan XML Views, jadi penting untuk mengenal keduanya.

8. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Android Development dengan Kotlin:

Pertanyaan 1: Apa perbedaan utama antara val dan var di Kotlin?

a) val untuk String, var untuk Int
b) val immutable (tidak bisa diubah), var mutable (bisa diubah)
c) val untuk private, var untuk public
d) Tidak ada perbedaan

Pertanyaan 2: Apa fungsi dari data class di Kotlin?

a) Mengakses database secara otomatis
b) Mengelola lifecycle Activity
c) Otomatis menghasilkan equals, hashCode, toString, dan copy
d) Membuat UI secara otomatis

Pertanyaan 3: Lifecycle method apa yang pertama kali dipanggil saat Activity dibuat?

a) onStart()
b) onResume()
c) onCreate()
d) onInit()

Pertanyaan 4: Mengapa RecyclerView lebih efisien daripada ListView?

a) RecyclerView menggunakan XML lebih sedikit
b) RecyclerView mendaur ulang view yang tidak terlihat (ViewHolder pattern)
c) RecyclerView tidak membutuhkan Adapter
d) RecyclerView bisa scroll otomatis

Pertanyaan 5: Apa keunggulan utama Jetpack Compose dibanding XML Views?

a) Lebih cepat di-compile
b) Declarative UI dengan state management yang terintegrasi
c) Tidak membutuhkan Kotlin
d) Hanya bisa untuk Android