Membuat aplikasi pencatat atau notes app adalah salah satu proyek awal yang sering dilakukan developer Android. Ini adalah cara yang bagus untuk memahami konsep dasar pengembangan aplikasi, mulai dari antarmuka pengguna (UI), persistensi data, hingga arsitektur aplikasi. Dengan Kotlin dan Room Database, kita bisa membangun aplikasi notes yang modern, efisien, dan maintainable.
Artikel ini akan memandu Anda langkah demi langkah untuk membangun aplikasi notes sederhana di Android menggunakan Kotlin sebagai bahasa pemrograman utama dan Room Database untuk mengelola data secara lokal. Kita akan menerapkan pola arsitektur MVVM (Model-View-ViewModel) secara fundamental untuk memastikan aplikasi kita terstruktur dengan baik.
Mengapa Kotlin dan Room Database?
Sebelum kita mulai, mari kita bahas mengapa kombinasi Kotlin dan Room Database adalah pilihan yang sangat kuat untuk proyek semacam ini:
- Kotlin: Bahasa pemrograman modern, ringkas, dan ekspresif yang sepenuhnya kompatibel dengan Java dan JVM. Fitur-fiturnya seperti null safety, ekstensi fungsi, dan coroutine sangat meningkatkan produktivitas dan mengurangi potensi bug. Google sendiri telah menjadikan Kotlin sebagai bahasa pilihan untuk pengembangan Android.
- Room Database: Bagian dari Android Jetpack, Room adalah lapisan abstraksi di atas SQLite yang mempermudah interaksi database. Dengan Room, Anda bisa menggunakan objek data (Entity) dan antarmuka DAO (Data Access Object) untuk melakukan operasi CRUD (Create, Read, Update, Delete) tanpa harus menulis banyak boilerplate code SQL. Room juga terintegrasi dengan LiveData dan Coroutine untuk operasi asinkron yang lebih mulus.
Persiapan Awal
Untuk mengikuti tutorial ini, pastikan Anda memiliki:
- Android Studio: Versi terbaru sangat disarankan.
- Pengetahuan Dasar Kotlin: Familiar dengan sintaks dasar Kotlin.
- Pengetahuan Dasar Android: Memahami konsep Activity, Layout XML, dan View.
Mari kita mulai!
Langkah 1: Setup Proyek Baru dan Dependencies
Pertama, buka Android Studio dan buat proyek baru:
- Pilih “New Project” dari layar selamat datang atau “File > New > New Project”.
- Pilih template “Empty Activity”.
- Konfigurasi proyek:
- Name: MyNotesApp (atau nama lain yang Anda inginkan)
- Package name: Pastikan unik, contoh:
com.yourcompany.mynotesapp - Save location: Pilih lokasi penyimpanan proyek.
- Language: Kotlin
- Minimum SDK version: Pilih API 21 atau lebih tinggi.
Setelah proyek terbuat, kita perlu menambahkan dependencies yang diperlukan ke file build.gradle.kts (Module :app). Tambahkan baris-baris berikut di dalam blok dependencies { ... }:
Untuk Room Database, ViewModel, LiveData, dan Coroutines:
implementation("androidx.room:room-runtime:2.6.1")
implementation("androidx.room:room-ktx:2.6.1")
kapt("androidx.room:room-compiler:2.6.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("androidx.activity:activity-ktx:1.9.0")
implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("com.google.android.material:material:1.12.0")
Pastikan juga Anda sudah menambahkan plugin kotlin-kapt di bagian atas file build.gradle.kts (Module :app) jika belum ada:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt") // Tambahkan ini
}
Sinkronkan proyek Anda setelah menambahkan dependencies.
Langkah 2: Mendesain Tampilan (UI) Aplikasi
Aplikasi notes kita akan memiliki dua layar utama:
MainActivity: Menampilkan daftar catatan dalam sebuahRecyclerViewdan sebuahFloatingActionButtonuntuk menambah catatan baru.AddEditNoteActivity: Layar untuk menambahkan atau mengedit catatan, dengan kolom input untuk judul dan konten, serta tombol simpan.
2.1. Layout untuk MainActivity (activity_main.xml)
Buka app/src/main/res/layout/activity_main.xml. Hapus isi default-nya dan tambahkan sebuah RecyclerView dan FloatingActionButton:
Struktur utama adalah ConstraintLayout. Di dalamnya, kita akan menempatkan sebuah RecyclerView yang akan mengisi seluruh layar untuk menampilkan daftar catatan. Di pojok kanan bawah, sebuah FloatingActionButton akan diletakkan untuk memicu aktivitas penambahan catatan baru.
RecyclerView: Berikan ID seperti@+id/notes_recycler_view. Atur lebar dan tinggi agar memenuhi parent.FloatingActionButton: Berikan ID seperti@+id/fab_add_note. Atursrcuntuk ikon tambah (misalnya,@android:drawable/ic_input_addatau buat ikon kustom Anda sendiri). Aturlayout_alignParentEnd="true"danlayout_alignParentBottom="true"atau constraint yang serupa jika menggunakanConstraintLayoutagar berada di pojok kanan bawah dengan margin yang sesuai.
2.2. Layout untuk Item Catatan (item_note.xml)
Buat file layout baru di app/src/main/res/layout/ dengan nama item_note.xml. File ini akan mendefinisikan tampilan setiap item catatan dalam RecyclerView:
Gunakan CardView untuk memberikan tampilan yang lebih modern dan terpisah untuk setiap catatan. Di dalam CardView, gunakan LinearLayout atau ConstraintLayout untuk menampung:
TextViewuntuk judul: Berikan ID@+id/text_view_title. Atur ukuran teks dan gaya agar menonjol.TextViewuntuk konten: Berikan ID@+id/text_view_content. Atur ukuran teks yang lebih kecil dan mungkin batas baris untuk pratinjau.
Tambahkan padding dan margin yang sesuai untuk estetika.
2.3. Layout untuk Add/Edit Note (activity_add_edit_note.xml)
Buat file layout baru di app/src/main/res/layout/ dengan nama activity_add_edit_note.xml. Ini adalah layar tempat pengguna memasukkan atau mengubah detail catatan:
Gunakan LinearLayout atau ConstraintLayout sebagai root. Di dalamnya, tempatkan:
EditTextuntuk judul: Berikan ID@+id/edit_text_title. Aturandroid:hint="Judul Catatan".EditTextuntuk konten: Berikan ID@+id/edit_text_content. Aturandroid:hint="Isi Catatan Anda". Setelandroid:gravity="top|start"agar teks mulai dari atas.Buttonuntuk menyimpan: Berikan ID@+id/button_save_note. Aturandroid:text="Simpan".
Tambahkan juga padding dan margin untuk membuat tampilan lebih rapi.
Langkah 3: Mengelola Data dengan Room Database
Room Database akan menangani persistensi data kita. Kita memerlukan Entity, DAO, dan Database itu sendiri.
3.1. Membuat Entitas Catatan (Note.kt)
Buat data class baru di folder app/src/main/java/com/yourcompany/mynotesapp/ dengan nama Note.kt. Ini akan merepresentasikan tabel di database Anda:
Gunakan anotasi @Entity(tableName = "notes_table") di atas deklarasi class. Untuk properti:
id: Int: Beri anotasi@PrimaryKey(autoGenerate = true)agar Room otomatis menghasilkan ID unik.title: String: Untuk judul catatan.content: String: Untuk isi catatan.
3.2. Membuat DAO (NoteDao.kt)
Buat antarmuka (interface) baru di lokasi yang sama dengan nama NoteDao.kt. DAO (Data Access Object) adalah tempat Anda mendefinisikan semua operasi database:
Deklarasikan sebagai interface dengan anotasi @Dao. Tambahkan fungsi-fungsi berikut:
@Insert suspend fun insert(note: Note): Untuk menyimpan catatan baru. Gunakansuspendkarena ini adalah operasi asinkron.@Update suspend fun update(note: Note): Untuk memperbarui catatan yang sudah ada.@Delete suspend fun delete(note: Note): Untuk menghapus catatan.@Query("SELECT * FROM notes_table ORDER BY id DESC") fun getAllNotes(): LiveData<List<Note>>: Untuk mendapatkan semua catatan.LiveDataakan secara otomatis memperbarui UI saat data berubah.
3.3. Membuat Database (NoteDatabase.kt)
Buat class abstrak baru di lokasi yang sama dengan nama NoteDatabase.kt. Ini adalah kelas database utama kita:
Deklarasikan sebagai abstract class yang mewarisi RoomDatabase(). Beri anotasi @Database(entities = [Note::class], version = 1). Anda harus mencantumkan semua entitas dan menentukan versi database.
Di dalamnya, buat sebuah fungsi abstrak untuk mengembalikan instance DAO Anda:
abstract fun noteDao(): NoteDao
Tambahkan juga sebuah companion object untuk membuat instance database singleton. Ini penting agar Anda hanya memiliki satu instance database di seluruh aplikasi, mencegah masalah konkurensi dan kebocoran memori:
- Buat sebuah variabel
volatileyang bisa menampung instance database. - Implementasikan metode
getDatabase(context: Context): NoteDatabaseyang akan membuat instance database menggunakanRoom.databaseBuilder()jika belum ada, atau mengembalikan instance yang sudah ada.
3.4. Membuat Repository (NoteRepository.kt)
Buat class baru di lokasi yang sama dengan nama NoteRepository.kt. Repository adalah lapisan abstraksi yang mengisolasi ViewModel dari sumber data langsung. Ini adalah praktik terbaik untuk arsitektur yang bersih:
Class ini akan menerima instance NoteDao di konstruktornya. Di dalamnya, Anda akan mengekspos metode-metode untuk mengakses data. Misalnya:
val allNotes: LiveData<List<Note>> = noteDao.getAllNotes()suspend fun insert(note: Note) { noteDao.insert(note) }suspend fun update(note: Note) { noteDao.update(note) }suspend fun delete(note: Note) { noteDao.delete(note) }
Fungsi-fungsi ini akan memanggil metode yang sesuai di NoteDao, biasanya dalam coroutine dispatchers (misalnya, Dispatchers.IO) untuk operasi I/O.
Langkah 4: ViewModel dan LiveData
ViewModel bertanggung jawab untuk menyiapkan dan menyediakan data untuk UI, serta bertahan dari perubahan konfigurasi (seperti rotasi layar).
Membuat ViewModel (NoteViewModel.kt)
Buat class baru di lokasi yang sama dengan nama NoteViewModel.kt yang mewarisi AndroidViewModel. ViewModel ini akan menerima instance Application dan NoteRepository:
Di dalamnya:
- Buat instance
NoteDaodariNoteDatabase. - Buat instance
NoteRepositorymenggunakanNoteDao. - Deklarasikan sebuah variabel
val allNotes: LiveData<List<Note>>dan inisialisasi denganrepository.allNotes.
Kemudian, buat fungsi-fungsi untuk operasi CRUD yang akan dipanggil dari UI:
fun insert(note: Note) = viewModelScope.launch(Dispatchers.IO) { repository.insert(note) }fun update(note: Note) = viewModelScope.launch(Dispatchers.IO) { repository.update(note) }fun delete(note: Note) = viewModelScope.launch(Dispatchers.IO) { repository.delete(note) }
Gunakan viewModelScope.launch untuk menjalankan operasi database di background thread.
Langkah 5: Menampilkan Data dengan RecyclerView
Sekarang kita akan mengimplementasikan RecyclerView untuk menampilkan catatan.
5.1. Membuat Adapter (NoteAdapter.kt)
Buat class baru di lokasi yang sama dengan nama NoteAdapter.kt yang mewarisi RecyclerView.Adapter<NoteAdapter.NoteViewHolder>. Adapter ini bertugas mengikat data ke tampilan item:
- Deklarasikan sebuah
inner class NoteViewHolderyang mewarisiRecyclerView.ViewHolderdan menampung referensi ke tampilan item (judul dan konten). - Implementasikan metode
onCreateViewHolder()untuk inflate layoutitem_note.xml. - Implementasikan metode
onBindViewHolder()untuk mengikat data dari objekNoteke tampilanNoteViewHolder. - Buat sebuah fungsi
setNotes(notes: List<Note>)untuk memperbarui daftar catatan dan memanggilnotifyDataSetChanged(). - Tambahkan listener untuk klik item atau klik hapus, misalnya dengan membuat callback interface atau fungsi lambda di konstruktor adapter.
5.2. Menghubungkan ke MainActivity (MainActivity.kt)
Buka MainActivity.kt. Kita akan menginisialisasi RecyclerView, Adapter, dan mengamati LiveData dari ViewModel:
Di dalam onCreate():
- Inisialisasi
RecyclerViewdari layout dan setelLinearLayoutManager. - Buat instance
NoteAdapter. - Setel adapter ke
RecyclerView. - Dapatkan instance
NoteViewModelmenggunakanViewModelProvider. - Amati
noteViewModel.allNotes. Setiap kali data catatan berubah,Observerakan dipanggil, dan Anda harus memanggiladapter.setNotes()untuk memperbarui tampilan. - Setel
OnClickListeneruntukFloatingActionButtonagar meluncurkanAddEditNoteActivity.
Jika Anda menambahkan fungsi edit atau delete ke adapter, pastikan untuk mengimplementasikan callback di MainActivity untuk memanggil metode update() atau delete() di NoteViewModel.
Untuk meluncurkan AddEditNoteActivity dan menerima hasilnya, gunakan registerForActivityResult:
val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
// Handle result here
}
Kemudian, ketika Anda ingin meluncurkan aktivitas:
startForResult.launch(intent)
Langkah 6: Menambah & Mengedit Catatan (AddEditNoteActivity.kt)
Layar ini akan menangani logika untuk membuat catatan baru atau mengedit catatan yang sudah ada.
Membuat AddEditNoteActivity (AddEditNoteActivity.kt)
Buat class baru di folder yang sama dengan nama AddEditNoteActivity.kt yang mewarisi AppCompatActivity. Setel layout ke activity_add_edit_note.xml.
Di dalam onCreate():
- Dapatkan referensi ke
EditTextuntuk judul dan konten, sertaButtonsimpan. - Periksa apakah ada data catatan yang dikirim melalui
Intent(ini menunjukkan mode edit). Jika ada, isiEditTextdengan data catatan yang ada. - Dapatkan instance
NoteViewModel. - Setel
OnClickListeneruntuk tombol simpan: - Ambil teks dari
EditText. - Validasi input (tidak boleh kosong).
- Jika ini mode edit, panggil
noteViewModel.update(). - Jika ini mode tambah, panggil
noteViewModel.insert(). - Setelah operasi selesai, setel
Result_OKuntukIntentdan panggilfinish()untuk kembali keMainActivity.
Langkah 7: Menghapus Catatan
Untuk menghapus catatan, kita bisa menambahkan fungsionalitas di NoteAdapter, misalnya dengan long click pada item atau menambahkan tombol hapus di setiap item.
Implementasi Hapus Catatan
Di NoteAdapter.kt:
- Buat callback atau lambda di konstruktor adapter, misalnya
onItemLongClickListener: ((Note) -> Unit)? = null. - Di
onBindViewHolder(), setelholder.itemView.setOnLongClickListener { ... }. Saat long click terjadi, panggilonItemLongClickListener?.invoke(currentNote).
Di MainActivity.kt:
- Saat menginisialisasi
NoteAdapter, setelonItemLongClickListener-nya. - Di dalam lambda, panggil
noteViewModel.delete(note)untuk menghapus catatan yang dipilih. - Anda juga bisa menambahkan
Snackbaruntuk mengkonfirmasi penghapusan atau memberikan opsi “Undo”.
Masalah yang Sering Terjadi
Dalam proses pengembangan aplikasi notes dengan Kotlin dan Room, beberapa masalah umum mungkin Anda temui:
1. Gagal Menemukan Symbol ‘kapt’ atau ‘AndroidViewModel’
Gejala: Android Studio menunjukkan error seperti “unresolved reference: kapt” atau “unresolved reference: AndroidViewModel”.
Penyebab: Biasanya ini terjadi karena plugin kotlin-kapt belum ditambahkan di build.gradle.kts (Module :app), atau dependencies untuk Room compiler/ViewModel belum ditambahkan atau versinya salah.
Solusi: Pastikan Anda telah menambahkan id("kotlin-kapt") di blok plugins dan semua dependencies yang disebutkan di Langkah 1 sudah benar dan di-sinkronkan.
2. Database Error: “Cannot access database on the main thread since it may potentially lock the UI for a long period of time.”
Gejala: Aplikasi crash dengan pesan error yang jelas menyatakan mencoba mengakses database di main thread.
Penyebab: Operasi database adalah I/O-bound dan harus dijalankan di background thread untuk menghindari UI beku. Room secara default akan mencegah operasi di main thread.
Solusi: Pastikan semua operasi database (insert, update, delete, query yang tidak mengembalikan LiveData) dijalankan dalam coroutine dengan Dispatchers.IO, seperti yang dilakukan di NoteViewModel menggunakan viewModelScope.launch(Dispatchers.IO) { ... }.
3. Data Tidak Muncul atau Tidak Terupdate di RecyclerView
Gejala: Catatan berhasil ditambahkan atau diubah, tetapi RecyclerView di MainActivity tidak menampilkan perubahan.
Penyebab: Ada beberapa kemungkinan:
- Observer LiveData tidak diatur dengan benar di
MainActivity. adapter.setNotes()tidak dipanggil atau tidak memanggilnotifyDataSetChanged()setelah memperbarui daftar di adapter.- Data tidak benar-benar tersimpan ke database.
Solusi:
- Pastikan
noteViewModel.allNotes.observe(this) { notes -> adapter.setNotes(notes) }ada dan berfungsi diMainActivity. - Di
NoteAdapter, pastikan fungsisetNotesAnda memperbarui list internal dan memanggilnotifyDataSetChanged(). - Periksa logcat untuk error database saat menyimpan atau mengambil data.
4. Crash Saat Mengedit Catatan: ID Tidak Ditemukan
Gejala: Aplikasi crash saat mencoba mengedit catatan, seringkali karena Note yang dikirim tidak memiliki ID yang benar (atau ID-nya 0).
Penyebab: Saat membuat catatan baru, ID diatur otomatis oleh Room. Namun, saat mengedit, Anda perlu mengirim catatan dengan ID yang sudah ada ke update(). Jika Anda membuat objek Note baru tanpa ID yang valid, Room tidak tahu catatan mana yang harus diperbarui.
Solusi: Pastikan saat meluncurkan AddEditNoteActivity untuk mode edit, Anda mengirim objek Note lengkap dengan ID-nya melalui Intent. Kemudian di AddEditNoteActivity, saat menyimpan perubahan, pastikan objek Note yang akan di-update memiliki ID yang sama dengan catatan yang sedang diedit.
Pengalaman dan Pertimbangan Praktis
Membangun aplikasi notes sederhana adalah fondasi yang bagus, tetapi ada beberapa pertimbangan yang bisa meningkatkan kualitas dan fungsionalitas aplikasi di dunia nyata:
1. Validasi Input yang Lebih Robust
Dalam contoh ini, kita hanya melakukan validasi dasar apakah judul dan konten kosong. Dalam aplikasi nyata, Anda mungkin perlu validasi yang lebih kompleks, seperti batas panjang karakter, pembersihan input, atau validasi format tertentu. Ini akan mencegah data yang tidak valid masuk ke database dan meningkatkan pengalaman pengguna.
2. Penanganan Error yang Lebih Baik
Saat ini, penanganan error kita cukup minimal. Dalam skenario produksi, Anda perlu mengimplementasikan penanganan error yang lebih komprehensif untuk operasi database, misalnya dengan menggunakan blok try-catch di Repository atau ViewModel, dan memberikan umpan balik yang informatif kepada pengguna melalui Toast atau Snackbar.
3. Fitur UI/UX Tambahan
Aplikasi notes ini sangat dasar. Anda bisa menambahkan fitur-fitur seperti:
- Fungsi Pencarian: Menggunakan filter di
RecyclerViewatau query Room untuk mencari catatan berdasarkan kata kunci. - Sortir Catatan: Mengurutkan catatan berdasarkan tanggal dibuat, tanggal dimodifikasi, atau abjad.
- Mode Gelap (Dark Mode): Mendukung tema gelap untuk kenyamanan pengguna.
- Swipe-to-Delete: Interaksi UI yang umum untuk menghapus item dari daftar.
- Konfirmasi Hapus: Dialog konfirmasi sebelum menghapus catatan secara permanen.
- Kategori Catatan: Menambahkan kemampuan untuk mengkategorikan catatan.
4. Unit Testing
Untuk aplikasi yang lebih kompleks, menulis unit test untuk ViewModel dan Repository sangat dianjurkan. Ini memastikan bahwa logika bisnis Anda bekerja dengan benar dan tetap berfungsi saat ada perubahan kode di masa mendatang. Room Database juga menyediakan library testing yang memungkinkan Anda menguji DAO dan entitas secara terisolasi.
5. Migrasi Database
Jika di masa depan Anda perlu mengubah skema database (misalnya, menambahkan kolom baru ke entitas Note), Anda harus mengimplementasikan migrasi database. Room memiliki mekanisme yang kuat untuk menangani migrasi ini agar data pengguna yang sudah ada tidak hilang. Kegagalan migrasi adalah salah satu penyebab crash terbesar di aplikasi yang sudah rilis.
6. Pertimbangan Resource
Untuk aplikasi notes sederhana, penggunaan resource (CPU, memori, baterai) biasanya minimal. Namun, jika Anda menambahkan fitur seperti sinkronisasi cloud, gambar, atau rich text editor, perhatikan penggunaan resource dan lakukan optimasi yang diperlukan.
FAQ
Apa itu Room Database dan mengapa lebih baik dari SQLite?
Room Database adalah lapisan abstraksi di atas SQLite yang mempermudah interaksi dengan database. Room lebih baik karena mengurangi boilerplate code, menyediakan verifikasi query saat kompilasi (compile-time verification), terintegrasi dengan LiveData dan Coroutine, serta mendorong pola arsitektur yang lebih bersih.
Apakah saya perlu menggunakan Coroutines untuk semua operasi Room?
Ya, sangat disarankan. Room secara default tidak mengizinkan operasi database di Main Thread karena bisa memblokir UI. Menggunakan Coroutines dengan Dispatchers.IO memastikan operasi database berjalan di background thread, menjaga aplikasi tetap responsif.
Apa perbedaan antara ViewModel dan LiveData?
ViewModel bertanggung jawab untuk menyiapkan dan mengelola data yang terkait dengan UI, dan bertahan dari perubahan konfigurasi (misalnya rotasi layar). Sedangkan LiveData adalah data holder yang dapat diobservasi dan lifecycle-aware. LiveData akan memberitahu Observer (biasanya UI) ketika data berubah, tetapi hanya jika Observer berada dalam keadaan aktif (misalnya Activity atau Fragment yang sedang berjalan).
Bagaimana cara membuat catatan baru sebagai Primary Key?
Untuk membuat primary key secara otomatis saat catatan baru ditambahkan, Anda harus mendeklarasikan properti ID di entitas Note dengan anotasi @PrimaryKey(autoGenerate = true). Room akan secara otomatis menghasilkan nilai unik untuk ID tersebut setiap kali Anda menyisipkan catatan baru.
Bisakah saya menambahkan gambar atau format teks kaya ke catatan?
Secara teknis bisa, namun akan membuat aplikasi jauh lebih kompleks. Untuk gambar, Anda perlu menyimpan path atau URI gambar dan mungkin mengelola penyimpanan eksternal. Untuk teks kaya, Anda memerlukan pustaka pihak ketiga atau implementasi kustom untuk editor teks. Ini memerlukan pengembangan lebih lanjut di luar cakupan tutorial dasar ini.
Kesimpulan
Selamat! Anda telah berhasil membangun aplikasi notes dasar di Android menggunakan Kotlin dan Room Database. Ini adalah fondasi yang kokoh untuk memahami bagaimana aplikasi Android modern dibangun, mulai dari UI, persistensi data, hingga pola arsitektur MVVM.
Meskipun aplikasi ini sederhana, konsep-konsep yang Anda pelajari—seperti Entity, DAO, Room Database, Repository, ViewModel, LiveData, dan RecyclerView—adalah blok bangunan esensial dalam pengembangan aplikasi Android yang lebih kompleks. Jangan ragu untuk bereksperimen, menambahkan fitur-fitur baru, dan menyesuaikannya sesuai kebutuhan Anda. Praktik adalah kunci untuk menjadi developer Android yang mahir.
TAGS: Kotlin, Android Development, Notes App, Room Database, RecyclerView, LiveData, ViewModel, CRUD, Tutorial, Mobile Development


