Episode ini membuat aplikasi bekerja offline: integrasi Room dengan Flow, Preferences DataStore untuk preferensi, state restoration dengan rememberSaveable dan SavedStateHandle, serta pola offline-first dengan repository layer.

Aplikasi yang baik tetap berguna tanpa koneksi. Episode 11 membekali kalian dengan penyimpanan data lokal: Room untuk database relasional, DataStore untuk preferensi, dan pola restoration agar state tidak hilang saat aplikasi di-recreate.
Di Compose, penyimpanan dan UI bertemu lewat Flow: database mengekspos aliran data, UI meng-collect-nya, dan perubahan langsung tercermin. Inilah pola data-driven UI yang juga membentuk arsitektur offline-first.
Episode 11 ini meliputi Room, DataStore, state restoration, dan repository layer.
Room menyimpan data relasional di SQLite dengan type safety. Definisikan Entity, DAO, dan Database:
@Entity(tableName = "tugas")
data class Tugas(
@PrimaryKey val id: Long,
val judul: String,
val selesai: Boolean = false
)
@Dao
interface TugasDao {
@Insert
suspend fun tambah(tugas: Tugas)
@Query("SELECT * FROM tugas ORDER BY id DESC")
fun semuaTugas(): Flow<List<Tugas>>
}@Dao dengan return type Flow<List<Tugas>> membuat UI selalu sinkron: setiap perubahan tabel memancarkan data baru. Anotasi Room seperti @PrimaryKey diproses oleh KSP di build Gradle.
Room memakai KSP untuk memproses anotasi. Tambahkan plugin dan dependency di build Gradle:
plugins {
id("com.google.devtools.ksp")
}
dependencies {
implementation("androidx.room:room-runtime:2.7.0")
implementation("androidx.room:room-ktx:2.7.0")
ksp("androidx.room:room-compiler:2.7.0")
}androidx.room:room-ktx menyediakan dukungan coroutines dan Flow, dan ksp menjalankan code generation. Setelah sinkronisasi, kalian bisa mengecek kelancaran konfigurasi dengan ./gradlew :app:build.
Untuk key-value kecil seperti preferensi theme, pakai Preferences DataStore:
val Context.dataStore by preferencesDataStore(name = "preferensi")
val darkModePref: Flow<Boolean> = context.dataStore.data
.map { it[prefsKeyDarkMode] ?: false }preferencesDataStore(name = "preferensi") membuat instance DataStore, dan dataStore.data adalah Flow yang bisa di-collect di Compose. Nilai yang dibaca bisa langsung menggerakkan theme dari episode 6.
Di episode 5 kalian memakai rememberSaveable untuk satu nilai. Untuk objek kompleks, daftarkan Saver:
val DaftarSaver = listSaver<DaftarTugas, String>(
save = { it.items },
restore = { DaftarTugas(it) }
)
var daftar by rememberSaveable(saver = DaftarSaver) {
mutableStateOf(DaftarTugas(emptyList()))
}Saver mengubah objek menjadi nilai yang bisa disimpan di Bundle dan mengembalikannya lagi. listSaver menyimpan isi list dan restore membangun ulang objek saat Activity di-recreate.
Untuk state ViewModel, SavedStateHandle menyimpan nilai melewati process death:
class FormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
var nama: String
get() = savedStateHandle["nama"] ?: ""
set(value) { savedStateHandle["nama"] = value }
}savedStateHandle["nama"] otomatis dipulihkan setelah recreation. Kombinasi rememberSaveable di UI dan SavedStateHandle di ViewModel membangun restoration yang lengkap.
Pola offline-first: UI membaca dari repository, repository memutuskan data lokal atau jaringan:
class ArtikelRepository(
private val lokal: ArtikelDao,
private val remote: ArtikelApi
) {
fun semuaArtikel(): Flow<List<Artikel>> =
lokal.semuaArtikel().flatMapLatest { lokalList ->
if (lokalList.isEmpty()) {
refreshDariJaringan()
}
lokal.semuaArtikel()
}
private suspend fun refreshDariJaringan() {
val remoteList = remote.fetch()
lokal.upsert(remoteList)
}
}Repository menyembunyikan sumber data dari UI. flatMapLatest memicu refresh saat cache kosong, dan hasil jaringan disimpan ke Room — sekali tersimpan, daftar tampil offline.
ViewModel mengekspos Flow repository, dan UI meng-collect dengan pola dari episode 10:
val artikel by viewModel.artikelFlow.collectAsStateWithLifecycle()
LazyColumn {
items(artikel, key = { it.id }) { item ->
KartuArtikel(item)
}
}collectAsStateWithLifecycle menghubungkan Room, repository, dan UI. Karena seluruh lapisan data mengalir sebagai Flow, offline dan online sama-sama tampil mulus.
Episode 11 membangun persistensi: Room dengan Flow dan KSP untuk data relasional, Preferences DataStore untuk preferensi, state restoration dengan Saver dan SavedStateHandle, serta pola offline-first dengan repository yang memutuskan sumber data dan membuat UI digerakkan data.
Inti yang harus dibawa pulang:
Di episode 12 selanjutnya kita akan membahas animations dan motion — animate*AsState untuk transisi halus, AnimatedVisibility dan updateTransition, animasi berbasis fisika, serta polish UI dengan gesture.