Room Database (RoomDB) adalah library persistence di Android yang menyediakan lapisan abstraksi di atas SQLite untuk memudahkan pengelolaan database lokal. Dalam Android Jetpack Compose, Room Database memungkinkan aplikasi untuk menyimpan, mengambil, dan memanipulasi data secara efisien dalam aplikasi berbasis UI deklaratif.
RoomDB mendukung pengoperasian data yang aman dan memudahkan developer dalam beberapa hal, antara lain:
Mapping Object ke Database: RoomDB memungkinkan developer untuk mendefinisikan entitas (data class) yang akan secara otomatis dipetakan ke tabel database SQLite.
Query dengan Anotasi: Query dijalankan dengan menggunakan anotasi @Query
yang dapat memetakan hasil SQL langsung ke objek Kotlin. Ini meminimalkan kesalahan query manual.
Keamanan Data: Room secara otomatis menangani akses thread untuk database, jadi operasi seperti INSERT
, UPDATE
, dan DELETE
dapat dijalankan secara asinkron dengan aman.
Integrasi LiveData atau Flow: RoomDB mendukung penggunaan LiveData
atau Flow
untuk memantau data secara reaktif. Ini sangat berguna di Jetpack Compose, yang mendukung UI reaktif.
Berikut ini kita akan mencoba membuat project menggunakan RoomDB pada android jetpack compose MVVM. project ini hanya akan mengelakukan aktifitas seperti menambah, mengubah, menghapus dan menampilkan data user.
Buat project baru pada android studio, kemudian pilih empty aktivity compose. berinama RoomDB
Selanjutnya, buka file build.grandle.kts lalu tambahkan didalam body plugin{ }
id("kotlin-kapt")
. maka akan terlihat seperti ini :
plugins **{**
alias(*libs*.*plugins*.*android*.*application*)
alias(*libs*.*plugins*.*jetbrains*.*kotlin*.*android*)
id("kotlin-kapt")
**}**
Masih difile yang sama tambahkan beberapa dependency didalam body dependency{} seperti dibawah ini:
val room_version = "2.6.1"
*implementation*("androidx.room:room-runtime:$room_version")
*kapt*("androidx.room:room-compiler:$room_version")
*implementation*("androidx.room:room-ktx:$room_version")
val lifecycle_version = "2.8.7"
val arch_version = "2.2.0"
*implementation*("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version")
*implementation*("androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version")
val nav_version = "2.8.3"
*implementation*("androidx.navigation:navigation-compose:$nav_version")
Selanjutnya, kita akan membuat entity file User.kt yang mempresentasikan kolom pada tabel yang akan kita buat, dalam kasus ini kita membuat tabel user. seperti berikut :
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0L,
val name : String,
val kelas : String,
val kategori : String
)
Selanjut, Kita akan membuat file UsersDao.kt dimana kita akan memberikan aktifitas apa saja yang bisa dilakukan pada tabel users. Seperti berikut :
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
@Dao
abstract class UsersDao {
// Insert Data
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract suspend fun addUsers(usersEntity : User)
// Get All Data
@Query("select \* from \`users\`")
abstract fun getAllUsers() : Flow<List<User>>
// Get Single Data
@Query("select \* from \`users\` where id = :id")
abstract fun getSingleUser(id:Long) : Flow<User>
@Update
abstract suspend fun updateUsers(usersEntity: User)
@Delete
abstract suspend fun deleteUsers(usersEntity: User)
}
Selanjutnya, Kita membuat UsersRepository.kt yang berfungsi untuk menghubungkan UserDao ke viewmodel. seperti berikut :
import kotlinx.coroutines.flow.Flow
class UsersRepository(private val usersDao: UsersDao) {
suspend fun addUsers(user: User){
usersDao.addUsers(user)
}
fun getUser() : Flow<List<User>> = usersDao.getAllUsers()
fun getUserById(id : Long) : Flow<User>{
return usersDao.getSingleUser(id)
}
suspend fun updateUser(user: User){
usersDao.updateUsers(user)
}
suspend fun deleteUser(user: User){
usersDao.deleteUsers(user)
}
}
Lalu, Kita membuat file database.kt. dimana file tersebut akan mengumpulkan semua file entity (User.kt). seperti berikut :
import androidx.room.Database
import androidx.room.RoomDatabase
import com.example.roomdb.data.users.User
import com.example.roomdb.data.users.UsersDao
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class Database : RoomDatabase() {
abstract fun usersDao(): UsersDao
}
Selanjutnya, kita membuat object file Graph.kt. dimana file ini nanti akan diakses oleh sistem yang dijalankan dilatar belakang, seperti berikut:
import android.content.Context
import androidx.room.Room
import com.example.roomdb.data.users.UsersRepository
object Graph {
lateinit var database : Database
val userRepository by *lazy* **{**
UsersRepository(usersDao = database.usersDao())
**}**
fun provide(context: Context){
database = Room.databaseBuilder(context, Database::class.*java*, "users.db").build()
}
}
Selanjutnya, kita akan membuat file baru yang bernama UserApplication.kt yang dimana file ini akan menjalankan object Graph file dilatar belakang, seperti berikut:
import android.app.Application
import com.example.roomdb.data.Graph
class UserAplication : Application() {
override fun onCreate() {
super.onCreate()
Graph.provide(this)
}
}
10. Selanjutnya. kita akan memodifikasi AndroidManifest.xml, dengan menambahkan
android:name=".UserAplication"
didalam tag <application :>, seperti berikut :
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
**android:name=".UserAplication"**
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.RoomDB"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.RoomDB">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Sampai disini, semua tahapan untuk membuat Database beserta aktifitasnya telah selesai. selanjutkan kita akan membuat tampilan dan navigasinya.
Kita akan membuat file ViewModel bernama UserViewModel.kt. file ini akan menjembatani alur data dari database ke tampilan. seperti berikut:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.*viewModelScope*
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.roomdb.data.Graph
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
class UserViewModel(
private val usersRepository: UsersRepository = Graph.userRepository
) : ViewModel() {
var _name by *mutableStateOf*("")
var validate_name by *mutableStateOf*(false)
var _kelas by *mutableStateOf*("")
var validate_kelas by *mutableStateOf*(false)
var _kategori by *mutableStateOf*("")
var validate_kategori by *mutableStateOf*(false)
fun onChangeName(newName : String){
_name = newName
validate_name = if (newName.*isEmpty*()) true else false
}
fun onChangeKelas(kelas : String){
_kelas = kelas
validate_kelas = if (kelas.*isEmpty*()) true else false
}
fun onChangeKategori(kategori : String){
_kategori = kategori
validate_kategori = if (kategori.*isEmpty*()) true else false
}
lateinit var getAllIUsers : Flow<List<User>>
init {
*viewModelScope*.*launch* **{**
getAllIUsers = usersRepository.getUser()
**}**
}
fun getUserById(id:Long) : Flow<User>{
return usersRepository.getUserById(id)
}
fun addUser(user: User){
*viewModelScope*.*launch* **{**
usersRepository.addUsers(user)
**}**
}
fun editUser(user: User){
*viewModelScope*.*launch* **{**
usersRepository.updateUser(user)
**}**
}
fun delUser(user: User){
*viewModelScope*.*launch* **{**
usersRepository.deleteUser(user)
**}**
}
}
Selanjutnya, kita membuat file baru berinama ListUser.kt untuk tampilan daftar user. seperti berikut :
import android.widget.Space
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CardElevation
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import com.example.roomdb.data.users.User
import com.example.roomdb.data.users.UserViewModel
@Composable
fun ListUser(viewModel: UserViewModel, navController: NavController) {
Column(
modifier = Modifier
.*fillMaxSize*()
.*padding*(12.*dp*)
) **{**
Text(text = "Daftar User")
Spacer(modifier = Modifier.*height*(10.*dp*))
Row **{**
TextButton(onClick = **{**
navController.navigate("add_form/0L")
**}**) **{**
Text("Tambah User")
**}
}
**val list_user = viewModel.getAllIUsers.collectAsState(initial = *listOf*())
LazyColumn(
modifier = Modifier.*fillMaxSize*()
) **{**
*items*(list_user.value, key = **{** item **->** item.id **}**) **{**
myModel **->** CardUser(viewModel,myModel, navController)
**}
}
}
**}
@Composable
fun CardUser(viewModel: UserViewModel,user : User, navController: NavController) {
Card(
modifier = Modifier
.*fillMaxWidth*()
.*padding*(12.*dp*)
) **{**
Column(modifier = Modifier.*padding*(6.*dp*)) **{**
Text(user.name)
Spacer(modifier = Modifier.*width*(10.*dp*))
Text("${user.kategori} : ${user.kelas}")
Spacer(modifier = Modifier.*width*(10.*dp*))
Row **{**
TextButton(onClick = **{**
navController.navigate("add_form/${user.id}")
**}**) **{**
Text("Edit")
**}**
Spacer(modifier = Modifier.*width*(10.*dp*))
TextButton(onClick = **{**
viewModel.delUser(user)
**}**) **{**
Text("Delete")
**}
}
}
}
**}
Selanjutnya, kita akan membuat file untuk menampilkan form yang berfungsi untuk menambah dan mengubah data user, beri nama file tersebut Add.kt, seperti berikut :
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import com.example.roomdb.data.users.User
import com.example.roomdb.data.users.UserViewModel
import kotlinx.coroutines.launch
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun Add(id:Long,navController: NavController){
val viewModel : UserViewModel = viewModel()
val scope = rememberCoroutineScope()
val snakebarHostState = remember **{**
SnackbarHostState()
**}**
var snackbarMessage = remember **{**
*mutableStateOf*("")
**}**
if(id!=0L){
val users = viewModel.getUserById(id).collectAsState(initial = User(
id = 0L,
name = "",
kelas = "",
kategori = "",
))
viewModel._name = users.value.name
viewModel._kelas = users.value.kelas
viewModel._kategori = users.value.kategori
}else{
viewModel._name = ""
viewModel._kelas = ""
viewModel._kategori = ""
}
Scaffold(
snackbarHost = **{**
SnackbarHost(hostState = snakebarHostState)
**}**
) **{**
Column(
modifier = Modifier.*fillMaxSize*(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) **{**
OutlinedTextField(value = viewModel._name, onValueChange = **{**
viewModel.onChangeName(**it**)
**}**, placeholder = **{**
Text("Nama")
**}**, isError = viewModel.validate_name)
Spacer(modifier = Modifier.*height*(6.*dp*))
OutlinedTextField(value = viewModel._kelas, onValueChange = **{**
viewModel.onChangeKelas(**it**)
**}**, placeholder = **{**
Text("Kelas")
**}**, isError = viewModel.validate_kelas)
Spacer(modifier = Modifier.*height*(6.*dp*))
OutlinedTextField(value = viewModel._kategori, onValueChange = **{**
viewModel.onChangeKategori(**it**)
**}**, placeholder = **{**
Text("Kategori")
**}**, isError = viewModel.validate_kategori)
Spacer(modifier = Modifier.*height*(6.*dp*))
Row **{**
TextButton(onClick = **{**
if(
viewModel._name.*isNotEmpty*() &&
viewModel._kategori.*isNotEmpty*() &&
viewModel._kelas.*isNotEmpty*()
) {
if(id != 0L){
//Update Data
viewModel.editUser(
User(
id = id,
name = viewModel._name.*trim*(),
kategori = viewModel._kategori.*trim*(),
kelas = viewModel._kelas.*trim*()
)
)
snackbarMessage.value = "User telah berhasil diubah"
}else{
// Create Data
viewModel.addUser(
User(
name = viewModel._name.*trim*(),
kategori = viewModel._kategori.*trim*(),
kelas = viewModel._kelas.*trim*()
)
)
snackbarMessage.value = "User telah berhasil ditambahkan"
}
}else{
snackbarMessage.value = "Lengkapi Form Input Anda"
}
scope.*launch* **{**
snakebarHostState.showSnackbar(snackbarMessage.value)
navController.popBackStack()
**}**
) **{**
Text("Simpan")
**}**
Spacer(modifier = Modifier.*width*(10.*dp*))
TextButton(onClick = **{**
navController.popBackStack()
**}**) **{**
Text("Kembali")
**}
}
}
}
**}
Terakhir, modifikasi MainActivity.kt untuk menambahkan navigasi didalamnya, seperti berikut:
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModel
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigation
import androidx.navigation.compose.rememberNavController
import com.example.roomdb.data.users.UserViewModel
import com.example.roomdb.ui.theme.Add
import com.example.roomdb.ui.theme.RoomDBTheme
import com.example.roomdb.ui.theme.screen.ListUser
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavType
import androidx.navigation.navArgument
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
*setContent* **{**
RoomDBTheme **{**
Surface(
modifier = Modifier.*fillMaxSize*()
) **{**
val navController = rememberNavController()
val viewModel : UserViewModel = viewModel()
MainGraph(viewModel = viewModel, navController=navController)
**}
}
}
**}
}
@Composable
fun MainGraph(viewModel : UserViewModel = viewModel(),navController: NavController){
NavHost(navController = navController as NavHostController, startDestination = "list_user") **{**
*composable*("list_user")**{**
ListUser(viewModel,navController)
**}**
*composable*("add_form/{id}",
arguments = *listOf*(
*navArgument*("id")**{**
type = NavType.LongType
defaultValue = 0L
nullable = false
**}**
)
)**{** entry **->**
val id = if(entry.arguments !=null) entry.arguments!!.getLong("id") else 0L;
Add(id=id,navController)
**}
}
**}
Untuk mencobanya lakukan secara perlahan-lahan. Terimah kasih, Selamat Mencoba… :-)