Selasa, 05 Mei 2026

Android - Room Database

 


Room Database adalah bagian dari Android Jetpack yang menyediakan lapisan abstraksi di atas SQLite. Tujuannya adalah untuk mempermudah pengelolaan database lokal pada aplikasi Android dengan tetap memanfaatkan kekuatan penuh dari SQLite secara lebih aman dan efisien.

Secara sederhana, Room bertindak sebagai perantara antara kode Kotlin/Java Anda dengan database SQLite yang ada di sistem Android.


🧩 3 Komponen Utama Room

Ada tiga komponen penting yang harus ada agar Room bisa bekerja:

1. Entity

Entity adalah representasi dari tabel di dalam database. Anda mendefinisikan sebuah Data Class (di Kotlin) dan menandainya dengan anotasi @Entity. Setiap properti dalam kelas tersebut akan menjadi kolom di tabel database.

  • Anotasi Utama: @PrimaryKey (wajib ada satu), @ColumnInfo (untuk mengubah nama kolom).

2. DAO (Data Access Object)

DAO adalah antarmuka (interface) yang digunakan untuk mendefinisikan metode akses data. Di sinilah Anda menulis query SQL (seperti SELECT, INSERT, DELETE, UPDATE).

  • Kelebihan: Room akan memeriksa syntax SQL Anda saat proses kompilasi (compile-time). Jika ada typo pada nama tabel, aplikasi tidak akan bisa di-build, sehingga mencegah error saat aplikasi dijalankan.

3. Room Database

Ini adalah titik akses utama dan pemegang koneksi ke database. Kelas ini harus berupa kelas abstrak yang mengekstensi RoomDatabase. Di sinilah Anda mendaftarkan Entity dan DAO.


🚀 Mengapa Menggunakan Room? (Kelebihan)

  1. Keamanan Compile-time: Room mengecek query SQL Anda sebelum aplikasi dijalankan. Jika query salah, Room akan memberitahu Anda lewat pesan error saat kompilasi.

  2. Boilerplate Code Berkurang: Anda tidak perlu lagi menulis kode panjang seperti ContentValues atau Cursor secara manual seperti pada SQLite tradisional.

  3. Integrasi dengan Library Lain: Room mendukung penuh Flow (Kotlin) atau LiveData untuk pembaruan data secara real-time (otomatis memperbarui UI saat data di DB berubah).

  4. Migrasi yang Terstruktur: Room menyediakan sistem yang jelas untuk mengubah skema database (misalnya menambah kolom baru) tanpa menghapus data pengguna yang sudah ada.


🛠️ Contoh Implementasi Sederhana

Jika kita menerapkan Room pada aplikasi Marketplace Siswa yang Anda buat sebelumnya, berikut adalah gambaran kodenya:

Step 1: Entity (Tabel Produk)

Kotlin
@Entity(tableName = "produk_table")
data class Produk(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val nama: String,
    val harga: Int,
    val deskripsi: String
)

Step 2: DAO (Akses Data)

Kotlin
@Dao
interface ProdukDao {
    @Query("SELECT * FROM produk_table ORDER BY id DESC")
    fun getAllProduk(): Flow<List<Produk>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertProduk(produk: Produk)

    @Delete
    suspend fun deleteProduk(produk: Produk)
}

Step 3: Database Instance

Kotlin
@Database(entities = [Produk::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun produkDao(): ProdukDao
}

⚠️ Aturan Penting Room

  • Jangan di Main Thread: Operasi database (Insert, Update, Delete) tidak boleh dilakukan di Main Thread (thread UI) karena dapat menyebabkan aplikasi membeku (ANR - App Not Responding). Gunakan Coroutines (suspend functions) untuk menjalankan operasi di background.

  • Singleton Pattern: Sangat disarankan untuk membuat instance database sebagai Singleton agar tidak memakan banyak memori karena pembuatan koneksi database adalah proses yang "mahal".

Dengan Room, aplikasi Marketplace Anda nantinya tidak akan kehilangan data saat HP dimatikan atau aplikasi ditutup, karena data tersimpan permanen di penyimpanan internal ponsel.


entu, mari kita tingkatkan aplikasi MarketSiswa agar lebih tangguh. Dengan integrasi Room Database, data produk yang ditambahkan siswa akan tersimpan secara permanen di memori ponsel.

Berikut adalah perubahan kode yang terstruktur menjadi 3 bagian utama: Dependensi, Arsitektur Room, dan Update UI.

1. Tambahkan Dependensi

Buka file build.gradle.kts (Module: app) dan tambahkan baris berikut agar fitur Room bisa digunakan:

dependencies {

    val room_version = "2.6.1"

    implementation("androidx.room:room-runtime:$room_version")

    implementation("androidx.room:room-ktx:$room_version")

    kapt("androidx.room:room-compiler:$room_version") // Gunakan 'ksp' jika proyek Anda mendukung KSP

}

2. Implementasi Arsitektur Room

Kita akan mendefinisikan tabel, cara akses data, dan koneksi database.

import androidx.room.*

import kotlinx.coroutines.flow.Flow


// --- ENTITY (Tabel) ---

@Entity(tableName = "products")

data class ProductEntity(

    @PrimaryKey(autoGenerate = true) val id: Long = 0,

    val name: String,

    val price: String,

    val description: String

)


// --- DAO (Data Access Object) ---

@Dao

interface ProductDao {

    @Query("SELECT * FROM products ORDER BY id DESC")

    fun getAllProducts(): Flow<List<ProductEntity>>


    @Insert(onConflict = OnConflictStrategy.REPLACE)

    suspend fun insertProduct(product: ProductEntity)

}


// --- DATABASE INSTANCE ---

@Database(entities = [ProductEntity::class], version = 1)

abstract class AppDatabase : RoomDatabase() {

    abstract fun productDao(): ProductDao


    companion object {

        @Volatile

        private var INSTANCE: AppDatabase? = null


        fun getDatabase(context: android.content.Context): AppDatabase {

            return INSTANCE ?: synchronized(this) {

                val instance = Room.databaseBuilder(

                    context.applicationContext,

                    AppDatabase::class.java,

                    "marketplace_db"

                ).build()

                INSTANCE = instance

                instance

            }

        }

    }

}

3. Update UI (MainActivity.kt)

Sekarang kita hubungkan UI dengan Database menggunakan collectAsState agar daftar produk otomatis terupdate saat ada data baru masuk.

@OptIn(ExperimentalMaterial3Api::class)

@Composable

fun MainScreen() {

    val context = androidx.compose.ui.platform.LocalContext.current

    val db = remember { AppDatabase.getDatabase(context) }

    val productDao = db.productDao()

    

    // Mengambil data dari database secara real-time

    val productList by productDao.getAllProducts().collectAsState(initial = emptyList())

    

    var currentScreen by remember { mutableStateOf("home") }

    val snackbarHostState = remember { SnackbarHostState() }

    val scope = rememberCoroutineScope()


    Scaffold(

        snackbarHost = { SnackbarHost(snackbarHostState) },

        // ... (TopBar & BottomBar sama seperti sebelumnya)

        floatingActionButton = {

            if (currentScreen == "home") {

                ExtendedFloatingActionButton(

                    onClick = { currentScreen = "add" },

                    containerColor = MaterialTheme.colorScheme.primaryContainer

                ) {

                    Icon(Icons.Default.Add, contentDescription = null)

                    Text("Jual")

                }

            }

        }

    ) { innerPadding ->

        Box(modifier = Modifier.padding(innerPadding)) {

            when (currentScreen) {

                "home" -> HomeScreen(productList)

                "add" -> AddProductScreen(

                    onProductAdded = { name, price, desc ->

                        scope.launch {

                            // Simpan ke database

                            productDao.insertProduct(

                                ProductEntity(name = name, price = price, description = desc)

                            )

                            currentScreen = "home"

                            snackbarHostState.showSnackbar("Produk tersimpan permanen!")

                        }

                    }

                )

                "profile" -> ProfileScreen()

            }

        }

    }

}


@Composable

fun HomeScreen(products: List<ProductEntity>) {

    LazyColumn(

        modifier = Modifier.fillMaxSize(),

        contentPadding = PaddingValues(16.dp),

        verticalArrangement = Arrangement.spacedBy(12.dp)

    ) {

        item {

            Text("Katalog Siswa", fontSize = 24.sp, fontWeight = FontWeight.ExtraBold)

            Spacer(Modifier.height(8.dp))

        }

        items(products) { product ->

            Card(

                modifier = Modifier.fillMaxWidth(),

                shape = RoundedCornerShape(16.dp)

            ) {

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

                    Text(product.name, fontWeight = FontWeight.Bold, fontSize = 18.sp)

                    Text("Rp ${product.price}", color = MaterialTheme.colorScheme.primary)

                    Text(product.description, fontSize = 14.sp, color = Color.Gray)

                }

            }

        }

    }

}


@Composable

fun AddProductScreen(onProductAdded: (String, String, String) -> Unit) {

    var name by remember { mutableStateOf("") }

    var price by remember { mutableStateOf("") }

    var desc by remember { mutableStateOf("") }

    var isLoading by remember { mutableStateOf(false) }

    val scope = rememberCoroutineScope()


    Column(modifier = Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {

        OutlinedTextField(value = name, onValueChange = { name = it }, label = { Text("Nama") }, modifier = Modifier.fillMaxWidth())

        OutlinedTextField(value = price, onValueChange = { price = it }, label = { Text("Harga") }, modifier = Modifier.fillMaxWidth())

        OutlinedTextField(value = desc, onValueChange = { desc = it }, label = { Text("Deskripsi") }, modifier = Modifier.fillMaxWidth())


        Button(

            onClick = {

                isLoading = true

                scope.launch {

                    delay(800) // Simulasi UX loading

                    onProductAdded(name, price, desc)

                    isLoading = false

                }

            },

            modifier = Modifier.fillMaxWidth(),

            enabled = name.isNotBlank() && !isLoading

        ) {

            if (isLoading) CircularProgressIndicator(size = 20.dp) else Text("Simpan")

        }

    }

}


Apa yang berubah?

  1. Penggunaan Flow: Di ProductDao, kita menggunakan Flow<List<ProductEntity>>. Ini membuat UI "berlangganan" pada database. Begitu data di DB berubah, UI otomatis update tanpa perlu refresh manual.

  2. Coroutine Scope: Proses simpan (insertProduct) ditandai sebagai suspend, jadi kita menjalankannya di dalam scope.launch agar tidak mengganggu kelancaran UI.

  3. Data Persisten: Sekarang, silakan coba tambah produk, lalu tutup aplikasi sepenuhnya (bahkan hapus dari Recent Apps). Saat dibuka kembali, produk Anda akan tetap ada di sana!

Apakah Anda ingin menambahkan fitur Hapus Produk (Delete) dengan cara menggeser (swipe) kartu produk?</List


https://gemini.google.com/app/56c46549a5bc1461

Tidak ada komentar:

Posting Komentar

React - Deploy

  https://www.youtube.com/watch?v=Ey_90l9GaAw https://www.youtube.com/watch?v=G6D9cBaLViA https://www.youtube.com/watch?v=hn1IkJk24ow https:...