Tugas PPB Pertemuan 13
Nathanael Valen Susilo
5025231099
Aplikasi Registrasi Siswa dengan Room Database
Aplikasi Registrasi Siswa adalah aplikasi Android sederhana untuk mengelola data siswa secara lokal. Aplikasi ini dibuat menggunakan Kotlin, Jetpack Compose, Material 3, ViewModel, Coroutine, Flow, dan Room Database. Tujuan utama aplikasi ini adalah memperlihatkan bagaimana fitur CRUD dapat diterapkan pada aplikasi Android modern dengan struktur kode yang rapi.
Pada aplikasi ini, pengguna dapat menambahkan data siswa melalui form nama dan email, melihat daftar siswa yang sudah tersimpan, mengedit data siswa, dan menghapus data siswa. Data yang dimasukkan akan disimpan ke database lokal menggunakan Room, sehingga data tetap tersedia walaupun aplikasi ditutup dan dibuka kembali.
Fitur Aplikasi
Fitur pertama adalah tambah siswa. Pengguna dapat mengisi nama dan email pada form, lalu menekan tombol `Tambah Siswa`. Setelah tombol ditekan, data akan dikirim ke ViewModel dan disimpan ke Room Database.
Fitur kedua adalah tampil data siswa. Semua data siswa yang tersimpan akan ditampilkan dalam bentuk daftar kartu. Setiap kartu menampilkan avatar inisial, nama siswa, email siswa, tombol edit, dan tombol hapus.
Fitur ketiga adalah edit siswa. Saat tombol edit ditekan, data siswa yang dipilih akan masuk kembali ke form. Tombol yang awalnya bertuliskan `Tambah Siswa` berubah menjadi `Update Siswa`. Setelah data diperbarui, aplikasi akan menyimpan perubahan ke database.
Fitur keempat adalah hapus siswa. Saat tombol hapus ditekan, data siswa akan dihapus dari database. Karena daftar siswa menggunakan Flow dari Room, tampilan akan otomatis berubah setelah data dihapus.
Fitur tambahan dari aplikasi ini adalah penyimpanan lokal dan dukungan offline. Karena menggunakan Room Database, data tersimpan di SQLite pada perangkat pengguna.
Struktur Project
Package utama aplikasi ini adalah `com.example.registrasisiswa`. Struktur project dibuat sederhana agar mudah dipahami. Kode dibagi menjadi tiga bagian utama, yaitu `data`, `ui`, dan `viewmodel`.
com.example.registrasisiswa
|
|-- data
| |-- Siswa.kt
| |-- SiswaDao.kt
| `-- AppDatabase.kt
|
|-- ui
| |-- MainScreen.kt
| |-- StudentItem.kt
| `-- FormInput.kt
|
|-- viewmodel
| `-- StudentViewModel.kt
|
`-- MainActivity.kt
Folder `data` digunakan untuk menyimpan semua kode yang berhubungan dengan Room Database. Di dalamnya terdapat entity `Siswa`, DAO `SiswaDao`, dan class database `AppDatabase`.
Folder `ui` digunakan untuk menyimpan komponen tampilan Jetpack Compose. File `MainScreen.kt` berisi layar utama, `FormInput.kt` berisi form input nama dan email, sedangkan `StudentItem.kt` berisi tampilan satu item siswa pada daftar.
Folder `viewmodel` berisi `StudentViewModel.kt`. File ini menjadi penghubung antara UI dan database. UI tidak langsung memanggil DAO, tetapi melalui ViewModel agar struktur aplikasi lebih rapi.
File `MainActivity.kt` adalah entry point aplikasi. File ini membuat database, mengambil DAO, membuat ViewModel, lalu menampilkan `MainScreen`.
Implementasi Room Database
Room Database pada aplikasi ini terdiri dari tiga bagian utama, yaitu Entity, DAO, dan Database Class. Entity digunakan untuk mendefinisikan bentuk tabel, DAO digunakan untuk mendefinisikan operasi database, dan Database Class digunakan untuk membuat instance database.
Entity Siswa
File `Siswa.kt` digunakan sebagai entity Room. Entity ini akan menjadi tabel bernama `siswa` di database lokal.
@Entity(tableName = "siswa")
data class Siswa(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val nama: String,
val email: String
)
Potongan kode di atas menunjukkan bahwa tabel `siswa` memiliki tiga kolom, yaitu `id`, `nama`, dan `email`. Kolom `id` dijadikan primary key dan nilainya dibuat otomatis oleh Room. Kolom `nama` digunakan untuk menyimpan nama siswa, sedangkan kolom `email` digunakan untuk menyimpan email siswa.
DAO Siswa
File `SiswaDao.kt` berisi daftar operasi database yang dapat dilakukan pada tabel `siswa`.
@Dao
interface SiswaDao {
@Query("SELECT * FROM siswa ORDER BY id DESC")
fun getAllSiswa(): Flow<List<Siswa>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSiswa(siswa: Siswa)
@Update
suspend fun updateSiswa(siswa: Siswa)
@Delete
suspend fun deleteSiswa(siswa: Siswa)
}
Fungsi `getAllSiswa()` digunakan untuk mengambil seluruh data siswa dari database. Query `ORDER BY id DESC` membuat data terbaru tampil di bagian paling atas. Fungsi ini mengembalikan `Flow<List<Siswa>>`, sehingga setiap perubahan data dapat langsung dikirim ke UI.
Fungsi `insertSiswa()` digunakan untuk menambahkan data siswa baru. Fungsi `updateSiswa()` digunakan untuk memperbarui data siswa yang sudah ada. Fungsi `deleteSiswa()` digunakan untuk menghapus data siswa. Semua fungsi perubahan data dibuat `suspend` karena operasi database sebaiknya dijalankan secara asynchronous.
Database Class
File `AppDatabase.kt` digunakan untuk membuat database Room.
@Database(entities = [Siswa::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun siswaDao(): SiswaDao
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,
"registrasi_siswa_db"
).build()
INSTANCE = instance
instance
}
}
}
}
Annotation `@Database` memberi tahu Room bahwa class ini adalah database utama. Parameter `entities = [Siswa::class]` berarti database ini menggunakan entity `Siswa`. Nama database yang dibuat adalah `registrasi_siswa_db`.
Bagian `companion object` digunakan untuk menerapkan singleton pattern. Dengan cara ini, aplikasi hanya membuat satu instance database. Annotation `@Volatile` membantu memastikan nilai `INSTANCE` selalu terbaca dengan benar ketika diakses dari thread yang berbeda.
Implementasi ViewModel
ViewModel digunakan agar UI tidak langsung berhubungan dengan DAO. Semua proses tambah, update, hapus, dan pengambilan data diatur melalui `StudentViewModel`.
class StudentViewModel(private val dao: SiswaDao) : ViewModel() {
val siswaList: StateFlow<List<Siswa>> = dao.getAllSiswa()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
fun simpanSiswa(nama: String, email: String, siswaEdit: Siswa?) {
val namaBersih = nama.trim()
val emailBersih = email.trim()
if (namaBersih.isEmpty() || emailBersih.isEmpty()) return
viewModelScope.launch {
if (siswaEdit == null) {
dao.insertSiswa(Siswa(nama = namaBersih, email = emailBersih))
} else {
dao.updateSiswa(siswaEdit.copy(nama = namaBersih, email = emailBersih))
}
}
}
}
Properti `siswaList` mengambil data dari `dao.getAllSiswa()`. Data yang awalnya berupa Flow diubah menjadi `StateFlow` menggunakan `stateIn()`. Ini membuat data mudah diamati oleh Compose.
Fungsi `simpanSiswa()` digunakan untuk dua kebutuhan sekaligus, yaitu tambah dan edit. Jika `siswaEdit` bernilai `null`, berarti pengguna sedang menambahkan siswa baru. Jika `siswaEdit` berisi data siswa, berarti pengguna sedang mengedit data yang sudah ada.
Operasi database dijalankan di dalam `viewModelScope.launch`. Tujuannya agar proses insert dan update berjalan di coroutine, bukan langsung di thread utama.
Untuk menghapus data, ViewModel menyediakan fungsi berikut.
fun hapusSiswa(siswa: Siswa) {
viewModelScope.launch {
dao.deleteSiswa(siswa)
}
}
Fungsi ini menerima data siswa yang dipilih dari UI, lalu memanggil `dao.deleteSiswa(siswa)` untuk menghapusnya dari database.
Implementasi UI dengan Jetpack Compose
Tampilan utama aplikasi berada di `MainScreen.kt`. Pada layar ini terdapat state untuk `nama`, `email`, dan `siswaEdit`.
val siswaList by viewModel.siswaList.collectAsState()
var nama by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var siswaEdit by remember { mutableStateOf<Siswa?>(null) }
`nama` dan `email` digunakan untuk menyimpan input dari form. `siswaEdit` digunakan untuk mengetahui apakah pengguna sedang menambah data baru atau sedang mengedit data lama. `siswaList` berasal dari ViewModel dan dibaca menggunakan `collectAsState()` agar dapat digunakan di Compose.
Form input dibuat dalam komponen `FormInput`.
FormInput(
nama = nama,
email = email,
sedangEdit = siswaEdit != null,
onNamaChange = { nama = it },
onEmailChange = { email = it },
onSubmit = {
viewModel.simpanSiswa(nama, email, siswaEdit)
if (nama.isNotBlank() && email.isNotBlank()) {
kosongkanForm()
}
}
)
Kode tersebut menunjukkan konsep state hoisting. State disimpan di `MainScreen`, lalu nilainya dikirim ke `FormInput`. Ketika input berubah, `FormInput` memanggil callback `onNamaChange` dan `onEmailChange` agar state di `MainScreen` ikut berubah.
Daftar siswa ditampilkan menggunakan `LazyColumn`.
items(siswaList, key = { it.id }) { siswa ->
StudentItem(
siswa = siswa,
onEdit = {
siswaEdit = it
nama = it.nama
email = it.email
},
onDelete = {
if (siswaEdit?.id == it.id) kosongkanForm()
viewModel.hapusSiswa(it)
}
)
}
Saat tombol edit ditekan, data siswa dimasukkan ke dalam form dengan mengisi state `nama`, `email`, dan `siswaEdit`. Saat tombol hapus ditekan, ViewModel akan menghapus data tersebut dari database. Jika data yang sedang diedit ikut dihapus, form akan dikosongkan.
Alur Kerja Aplikasi
Saat aplikasi dibuka, `MainActivity` membuat instance database menggunakan `AppDatabase.getDatabase(applicationContext)`. Setelah itu, aplikasi mengambil `SiswaDao` dari database. DAO tersebut dimasukkan ke dalam `StudentViewModelFactory`, lalu digunakan untuk membuat `StudentViewModel`.
Setelah ViewModel siap, `MainActivity` menampilkan `MainScreen`. `MainScreen` membaca daftar siswa dari ViewModel. Jika database kosong, aplikasi menampilkan pesan bahwa belum ada data siswa. Jika database berisi data, setiap siswa akan ditampilkan dalam bentuk kartu.
Ketika pengguna menambah data, UI memanggil ViewModel. ViewModel memanggil DAO. DAO menjalankan query Room. Room menyimpan data ke SQLite. Setelah data berubah, Flow mengirim data terbaru ke ViewModel dan UI otomatis diperbarui.
Komentar
Posting Komentar