Belajar Kotlin - Null-safe Data Handling
Episode 8 of 23

Belajar Kotlin - Null-safe Data Handling

Episode ini menguasai penanganan data yang aman dari null: data classes dan destructuring declarations, sealed classes dan sealed interfaces untuk state, Result type dengan error handling idiomatis, serta serialisasi data memakai kotlinx.serialization atau Jackson.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Setelah null safety dan functional programming, episode 8 menggabungkan keduanya untuk penanganan data yang aman dan idiomatis. Kalian akan belajar data classes dengan destructuring, sealed classes dan sealed interfaces untuk memodelkan state, Result type untuk error handling tanpa exception, dan serialisasi data ke JSON.

Pendekatan ini membentuk gaya khas Kotlin di dunia nyata: data dimodelkan secara eksplisit, kemungkinan null dan error menjadi bagian dari sistem tipe, dan serialisasi berjalan mulus dengan tooling bawaan.

Setelah episode ini, kalian akan merancang model data dan alur error yang jauh lebih mudah dipahami daripada gaya Java klasik.

Data Classes dan Destructuring

Data Class untuk Model Data

data class sudah diperkenalkan di episode 4. Di episode ini kita memakai kemampuannya secara penuh, terutama dengan destructuring declarations — memecah objek menjadi beberapa variabel sekaligus berdasarkan urutan properties:

KotlinDestructuring declaration
data class Point(val x: Int, val y: Int)
 
val p = Point(3, 5)
val (x, y) = p
println("x=$x, y=$y")

val (x, y) = p menugaskan p.x ke x dan p.y ke y secara otomatis. Destructuring juga bekerja pada Map.Entry, List, dan tipe lain yang menyediakan fungsi component1, component2, dan seterusnya.

Memakai Destructuring pada Data Umum

Pola yang sangat sering dipakai adalah memecah pasangan dan hasil query:

KotlinDestructuring map dan list
val harga = mapOf("kopi" to 15000, "teh" to 8000)
 
for ((nama, nilai) in harga) {
    println("$nama = $nilai")
}
val (a, b) = listOf("satu", "dua")

for ((nama, nilai) in harga) mengiterasi seluruh pasangan key-value tanpa mengakses .key dan .value secara manual. Destructuring membuat pembacaan data menjadi jauh lebih ringkas. Kalian bisa menguji contoh destructuring ini dengan kotlinc Main.kt -include-runtime -d main.jar.

Sealed Class dan Sealed Interface untuk State

Sealed Interface untuk Hierarki yang Terbuka

Sejak Kotlin 1.5, sealed interface memperluas konsep sealed class: subtipe bisa diimplementasikan di mana saja (bukan hanya satu file), selama masih dalam satu modul. Ini lebih fleksibel untuk model state yang besar:

KotlinSealed interface untuk UI state
sealed interface UiState {
    data object Loading : UiState
    data class Content(val items: List<String>) : UiState
    data class Error(val message: String) : UiState
}
 
fun render(state: UiState): String = when (state) {
    is UiState.Loading -> "Memuat..."
    is UiState.Content -> "Menampilkan ${state.items.size} item"
    is UiState.Error -> "Terjadi error: ${state.message}"
}

Compile-time exhaustiveness tetap berlaku: jika menambah subtype baru, when akan gagal kompilasi sampai semua cabang ditangani. Pola UiState seperti ini sangat umum di aplikasi Android dan frontend untuk memodelkan status layar.

Mengapa Sealed untuk State

Sealed class/interface memberi jaminan dua arah: compiler tahu semua kemungkinan state, dan pembaca kode tahu bahwa state terbatas pada himpunan yang didokumentasikan. Ini menghilangkan error "state tidak terduga" yang sering terjadi saat menggunakan nullable flags atau enum bertingkat.

Result Type dan Error Handling

Result untuk Operasi yang Bisa Gagal

Kotlin standard library menyediakan Result<T> untuk merepresentasikan hasil sukses atau gagal tanpa melempar exception:

KotlinResult dan runCatching
fun bagi(a: Int, b: Int): Result<Int> =
    runCatching { if (b == 0) throw IllegalArgumentException() else a / b }
 
val hasil = bagi(10, 0)
hasil.onSuccess { println("Hasil: $it") }
    .onFailure { println("Gagal: ${it.message}") }

runCatching menangkap exception dan membungkusnya menjadi Result. onSuccess dan onFailure menangani dua cabang tanpa try-catch manual. Pola ini membuat error handling menjadi eksplisit dalam alur data, bukan tersebar di blok try-catch.

Kapan Memakai Result

Gunakan Result untuk operasi yang kegagalannya adalah bagian normal dari alur bisnis — parsing input, panggilan jaringan, validasi. Simpan exception untuk kondisi yang benar-benar luar biasa seperti kesalahan pemrograman. Kombinasi Result dengan flatMap atau fold menciptakan alur yang mudah diikuti.

Serialization dengan kotlinx.serialization

Konfigurasi dan Pemakaian Dasar

kotlinx.serialization adalah library resmi JetBrains untuk serialisasi. Class diubah menjadi JSON dengan satu baris, asalkan plugin Gradle terpasang dan class diberi anotasi @Serializable:

KotlinSerialization dengan kotlinx
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
 
@Serializable
data class Pesan(val pengirim: String, val isi: String)
 
fun main() {
    val pesan = Pesan("Arman", "Halo dari Kotlin")
    val json = Json.encodeToString(pesan)
    println(json)
    val balik = Json.decodeFromString<Pesan>(json)
    println(balik.isi)
}

Output JSON yang dihasilkan: {"pengirim":"Arman","isi":"Halo dari Kotlin"}. Dengan plugin org.jetbrains.kotlin.plugin.serialization di build.gradle.kts, compiler membangkitkan serializer otomatis — tidak perlu annotation processing atau reflection.

Alternatif: Jackson

Jika kalian sudah memakai ekosistem Java, Jackson juga berfungsi penuh untuk class Kotlin dengan modul jackson-module-kotlin. Kelebihannya adalah ekosistem kaya untuk JSON config dan streaming. Untuk project Kotlin murni, kotlinx.serialization lebih ringan karena mendukung serialisasi tipe nullable secara eksplisit dan coroutines dengan baik.

Penutup

Episode 8 melengkapi penanganan data yang aman: destructuring untuk memecah data class, sealed class dan sealed interface untuk state yang terjamin, Result dan runCatching untuk error handling tanpa exception, serta serialisasi dengan kotlinx.serialization atau Jackson.

Inti yang harus dibawa pulang:

  • Destructuring val (a, b) = objek memecah data class berdasarkan urutan properties.
  • Sealed interface memberi model state yang exhaustive di level kompilasi.
  • runCatching dan Result menangani kegagalan tanpa blok try-catch tersebar.
  • Gunakan Result untuk error bisnis, exception untuk kondisi luar biasa.
  • kotlinx.serialization membuat serialisasi JSON tanpa reflection.
  • Jackson tetap relevan untuk codebase yang sudah berbasis ekosistem Java.

Di episode 9 selanjutnya kita akan membahas coroutines dan asynchronous programming — dasar coroutine dan suspending functions, coroutine scopes dan dispatchers, launch dan async, structured concurrency dengan exception handling, serta Flow API untuk reactive streams.