1. Sebelum memulai
Sebagian besar aplikasi berkualitas produksi memiliki data yang perlu disimpan, bahkan setelah pengguna menutup aplikasi. Misalnya, aplikasi dapat menyimpan playlist lagu, item dalam daftar tugas, catatan pengeluaran dan pendapatan, katalog konstelasi, atau histori data pribadi. Untuk sebagian besar kasus ini, Anda menggunakan database untuk menyimpan data persisten ini.
Room adalah library persistensi yang merupakan bagian dari Android Jetpack. Room adalah lapisan abstraksi di atas database SQLite. SQLite menggunakan bahasa khusus (SQL) untuk menjalankan operasi database. Room lebih menyederhanakan tugas-tugas penyiapan, konfigurasi, dan interaksi dengan database daripada menggunakan SQLite secara langsung. Room juga menyediakan pemeriksaan waktu kompilasi terhadap pernyataan SQLite.
Gambar di bawah menunjukkan kesesuaian Room dengan arsitektur keseluruhan yang direkomendasikan dalam kursus ini.
Prasyarat
- Mengetahui cara mem-build antarmuka pengguna (UI) dasar untuk aplikasi Android.
- Mengetahui cara menggunakan aktivitas, fragmen, dan tampilan.
- Mengetahui cara beralih di antara fragmen, dengan menggunakan Safe Args untuk meneruskan data di antara fragmen.
- Memahami komponen arsitektur Android
ViewModel
,LiveData
, danFlow
, serta mengetahui cara menggunakanViewModelProvider.Factory
untuk membuat instance ViewModels. - Memahami dasar-dasar konkurensi.
- Mengetahui cara menggunakan coroutine untuk tugas yang berjalan lama.
- Memiliki pemahaman dasar tentang database SQL dan bahasa SQLite.
Yang akan Anda pelajari
- Cara membuat dan berinteraksi dengan database SQLite menggunakan library Room.
- Cara membuat entity, DAO, dan class database.
- Cara menggunakan objek akses data (DAO) untuk memetakan fungsi Kotlin ke kueri SQL.
Yang akan Anda build
- Anda akan mem-build aplikasi Inventory yang menyimpan item inventaris ke dalam database SQLite.
Yang Anda perlukan
- Kode awal untuk aplikasi Inventory.
- Komputer yang dilengkapi Android Studio.
2. Ringkasan aplikasi
Dalam codelab ini, Anda akan menggunakan aplikasi awal yang disebut aplikasi Inventory, dan menambahkan lapisan database ke dalamnya menggunakan library Room. Versi final aplikasi menampilkan item daftar dari database inventory menggunakan RecyclerView
. Pengguna akan memiliki opsi untuk menambahkan item baru, memperbarui item yang sudah ada, dan menghapus item dari database inventory (Anda akan menyelesaikan fungsionalitas aplikasi di codelab berikutnya).
Berikut adalah screenshot dari versi final aplikasi.
3. Ringkasan aplikasi awal
Mendownload kode awal untuk codelab ini
Codelab ini menyediakan kode awal bagi Anda untuk diperluas dengan fitur yang dipelajari dalam codelab ini. Kode awal dapat berisi kode yang tidak asing bagi Anda dari codelab sebelumnya dan juga kode yang tidak Anda ketahui yang akan Anda pelajari di codelab berikutnya.
Jika Anda menggunakan kode awal dari GitHub, pastikan nama foldernya adalah android-basics-kotlin-inventory-app-starter
. Pilih folder ini saat Anda membuka project di Android Studio.
Untuk mendapatkan kode codelab ini dan membukanya di Android Studio, lakukan hal berikut.
Mendapatkan kode
- Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
- Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.
- Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
- Temukan file di komputer Anda (mungkin di folder Downloads).
- Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.
Membuka project di Android Studio
- Mulai Android Studio.
- Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.
Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.
- Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
- Klik dua kali pada folder project tersebut.
- Tunggu Android Studio membuka project.
- Klik tombol Run untuk mem-build dan menjalankan aplikasi. Pastikan aplikasi di-build seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi disiapkan.
Ringkasan kode awal
- Buka project dengan kode awal di Android Studio.
- Jalankan aplikasi di perangkat Android, atau di emulator. Pastikan emulator atau perangkat yang terhubung menjalankan API level 26 atau lebih tinggi. Database Inspector berfungsi paling baik pada emulator/perangkat yang menjalankan API level 26.
- Aplikasi tidak menampilkan data inventaris. Perhatikan FAB untuk menambahkan item baru ke database.
- Klik FAB. Aplikasi akan membuka layar baru tempat Anda dapat memasukkan detail item baru.
Masalah dengan kode awal
- Di layar Add Item, masukkan detail item. Ketuk Save. Fragmen add item tidak ditutup. Kembali menggunakan tombol kembali sistem. Item baru tidak disimpan dan tidak tercantum di layar inventory. Perhatikan bahwa aplikasi tidak lengkap dan fungsi tombol Save tidak diterapkan.
Dalam codelab ini, Anda akan menambahkan bagian database dari aplikasi yang menyimpan detail inventaris di database SQLite. Anda akan menggunakan library persistensi Room untuk berinteraksi dengan database SQLite.
Panduan kode
Kode awal yang Anda download memiliki tata letak layar yang telah dirancang sebelumnya untuk Anda. Di jalur ini, Anda akan berfokus untuk menerapkan logika database. Berikut adalah panduan singkat beberapa file untuk membantu Anda memulai.
main_activity.xml
Aktivitas utama yang menghosting semua fragmen lain dalam aplikasi. Metode onCreate()
mengambil NavController
dari NavHostFragment
dan menyiapkan panel tindakan untuk digunakan dengan NavController
.
item_list_fragment.xml
Layar pertama yang ditampilkan di aplikasi. Layar ini terutama berisi RecyclerView dan FAB. Anda akan mengimplementasikan RecyclerView nanti di jalur tersebut.
fragment_add_item.xml
Tata letak ini berisi kolom teks untuk memasukkan detail item inventaris baru yang akan ditambahkan.
ItemListFragment.kt
Fragmen ini sebagian besar berisi kode boilerplate. Pada metode onViewCreated()
, pemroses klik disetel pada FAB untuk membuka fragmen add item.
AddItemFragment.kt
Fragmen ini digunakan untuk menambahkan item baru ke dalam database. Fungsi onCreateView()
menginisialisasi variabel binding dan fungsi onDestroyView()
menyembunyikan keyboard sebelum menghancurkan fragmen.
4. Komponen utama Room
Kotlin menyediakan cara mudah untuk menangani data dengan memperkenalkan class data. Data ini diakses dan mungkin dimodifikasi menggunakan panggilan fungsi. Namun, dalam dunia database, Anda memerlukan tabel dan kueri untuk mengakses dan mengubah data. Komponen Room berikut membuat alur kerja ini menjadi lancar.
Terdapat tiga komponen utama dalam Room:
- Entity data menampilkan tabel di database aplikasi Anda. Entity data digunakan untuk memperbarui data yang disimpan dalam baris di tabel, dan untuk membuat baris baru untuk penyisipan.
- Objek akses data (DAO) menyediakan metode yang digunakan oleh aplikasi Anda untuk mengambil, memperbarui, menyisipkan, dan menghapus data dalam database.
- Class database memiliki database dan merupakan titik akses utama untuk koneksi yang mendasarinya ke database aplikasi Anda. Class database menyediakan aplikasi Anda dengan instance DAO yang terkait dengan database tersebut.
Anda akan menerapkan dan mempelajari komponen ini lebih lanjut nanti di codelab ini. Diagram berikut menunjukkan bagaimana komponen Room bekerja bersama-sama untuk berinteraksi dengan database.
Menambahkan library Room
Dalam tugas ini, Anda akan menambahkan library komponen Room yang diperlukan ke file Gradle Anda.
- Buka file gradle level modul,
build.gradle (Module: InventoryApp.app)
. Dalam blokdependencies
, tambahkan dependensi berikut untuk library Room.
// Room implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version"
5. Membuat Entity item
Class Entity menentukan tabel, dan setiap instance class ini mewakili baris dalam tabel database. Class entity memiliki pemetaan untuk memberi tahu Room tentang bagaimana class entity bermaksud untuk menampilkan dan berinteraksi dengan informasi dalam database. Di aplikasi Anda, entity akan menyimpan informasi tentang item inventaris seperti nama item, harga item, dan stok yang tersedia.
Anotasi @Entity
menandai class sebagai class Entity database. Untuk setiap class Entity, tabel database dibuat untuk menampung item. Setiap kolom Entity ditampilkan sebagai kolom dalam database, kecuali jika dinyatakan lain (lihat dokumen Entity untuk detailnya). Setiap instance entity yang disimpan dalam database harus memiliki kunci utama. Kunci utama digunakan untuk mengidentifikasi setiap catatan/entri dalam tabel database Anda secara unik. Setelah ditetapkan, kunci utama tidak dapat diubah, maka akan mewakili objek entity selama ada dalam database.
Dalam tugas ini, Anda akan membuat class Entity. Tentukan kolom untuk menyimpan informasi inventaris berikut untuk setiap item.
Int
untuk menyimpan kunci utama.String
untuk menyimpan nama item.double
untuk menyimpan harga item.Int
untuk menyimpan jumlah stok.
- Buka kode awal di Android Studio.
- Buat paket bernama
data
pada paket dasarcom.example.inventory
.
- Di dalam paket
data
, buat class Kotlin baru bernamaItem
. Class ini akan menampilkan entity database di aplikasi Anda. Di langkah berikutnya, Anda akan menambahkan kolom yang sesuai untuk menyimpan informasi inventaris. - Perbarui definisi class
Item
dengan kode berikut. Deklarasikanid
dari jenisInt
,itemName
dari jenisString,
itemPrice
dari jenisDouble
, danquantityInStock
dari jenisInt
sebagai parameter untuk konstruktor utama. Tentukan nilai default sebesar0
keid
. Langkah ini akan menjadi kunci utama, ID untuk mengidentifikasi setiap catatan/entri dalam tabelItem
Anda secara unik.
class Item(
val id: Int = 0,
val itemName: String,
val itemPrice: Double,
val quantityInStock: Int
)
Class data
Class data utamanya digunakan untuk menyimpan data di Kotlin. Class data akan ditandai dengan kata kunci data
. Objek class data Kotlin memiliki beberapa manfaat tambahan, compiler otomatis membuat utilitas untuk membandingkan, mencetak, dan menyalin seperti toString()
, copy()
, dan equals()
.
Contoh:
// Example data class with 2 properties.
data class User(val first_name: String, val last_name: String){
}
Untuk memastikan konsistensi dan perilaku yang bermakna dari kode yang dihasilkan, class data harus memenuhi persyaratan berikut:
- Konstruktor utama harus memiliki setidaknya satu parameter.
- Semua parameter konstruktor utama harus ditandai sebagai
val
atauvar
. - Class data tidak boleh berupa
abstract
,open
,sealed
, atauinner
.
Untuk mempelajari class Data lebih lanjut, lihat dokumentasinya.
- Konversikan class
Item
ke class data dengan mengawali definisi class-nya dengan kata kuncidata
.
data class Item(
val id: Int = 0,
val itemName: String,
val itemPrice: Double,
val quantityInStock: Int
)
- Di atas deklarasi class
Item
, beri anotasi pada class data dengan@Entity
. Gunakan argumentableName
untuk memberikanitem
sebagai nama tabel SQLite.
@Entity(tableName = "item")
data class Item(
...
)
- Untuk mengidentifikasi
id
sebagai kunci utama, beri anotasi pada propertiid
dengan@PrimaryKey
. Setel parameterautoGenerate
ketrue
sehinggaRoom
menghasilkan ID untuk setiap entity. Hal ini menjamin bahwa ID untuk setiap item bersifat unik.
@Entity(tableName = "item")
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
...
)
- Anotasikan properti yang tersisa dengan
@ColumnInfo
. AnotasiColumnInfo
digunakan untuk menyesuaikan kolom yang terkait dengan bidang tertentu. Misalnya, saat menggunakan argumenname
, Anda dapat menentukan nama kolom yang berbeda untuk kolom daripada nama variabel. Sesuaikan nama properti menggunakan parameter seperti yang ditunjukkan di bawah ini. Pendekatan ini mirip dengan menggunakantableName
untuk menentukan nama yang berbeda dengan database.
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "name")
val itemName: String,
@ColumnInfo(name = "price")
val itemPrice: Double,
@ColumnInfo(name = "quantity")
val quantityInStock: Int
)
6. Membuat DAO item
Objek Akses Data (DAO)
Objek Akses Data (DAO) adalah pola yang digunakan untuk memisahkan lapisan persistensi dengan bagian aplikasi lainnya dengan menyediakan antarmuka abstrak. Pemisahan ini mengikuti prinsip tanggung jawab tunggal, yang telah Anda lihat di codelab sebelumnya.
Fungsi DAO adalah untuk menyembunyikan semua kerumitan yang terlibat dalam menjalankan operasi database dalam lapisan persistensi yang mendasari dari bagian aplikasi lainnya. Hal ini memungkinkan lapisan akses data diubah secara terpisah dari kode yang menggunakan data.
Dalam tugas ini, Anda akan menentukan Objek Akses Data (DAO) untuk Room. Objek akses data merupakan komponen utama Room yang bertanggung jawab untuk menentukan antarmuka yang mengakses database.
DAO yang akan Anda buat akan menjadi antarmuka kustom yang menyediakan metode praktis untuk melakukan kueri/mengambil, memasukkan, menghapus, serta memperbarui database. Room akan menghasilkan implementasi dari class ini pada waktu kompilasi.
Untuk operasi database umum, library Room
menyediakan anotasi kemudahan, seperti @Insert
, @Delete
, dan @Update
. Untuk lainnya, terdapat anotasi @Query
. Anda dapat menulis kueri apa pun yang didukung oleh SQLite.
Sebagai bonus tambahan, saat Anda menulis kueri di Android Studio, compiler akan memeriksa kueri SQL untuk menemukan error sintaksis.
Untuk aplikasi inventory, Anda harus dapat melakukan hal berikut:
- Sisipkan atau tambahkan item baru.
- Perbarui item yang ada untuk memperbarui nama, harga, dan kuantitas.
- Dapatkan item tertentu berdasarkan kunci utamanya,
id
. - Dapatkan semua item, sehingga Anda dapat menampilkannya.
- Hapus entri di database.
Sekarang, implementasikan DAO item di aplikasi Anda:
- Dalam paket
data
, buat class KotlinItemDao.kt
. - Ubah definisi class menjadi
interface
dan beri anotasi dengan@Dao
.
@Dao
interface ItemDao {
}
- Di dalam isi antarmuka, tambahkan anotasi
@Insert
. Di bawah@Insert
, tambahkan fungsiinsert()
yang menggunakan instance classEntity
item
sebagai argumennya. Operasi database dapat memerlukan waktu yang lama untuk dijalankan, sehingga harus berjalan di thread terpisah. Buat fungsi sebagai fungsi penangguhan, sehingga fungsi ini dapat dipanggil dari coroutine.
@Insert
suspend fun insert(item: Item)
- Tambahkan argumen
OnConflict
dan tetapkan nilaiOnConflictStrategy.
IGNORE
. ArgumenOnConflict
memberi tahu Room apa yang harus dilakukan jika terjadi konflik. StrategiOnConflictStrategy.
IGNORE
mengabaikan item baru jika kunci utama tersebut sudah ada dalam database. Untuk mengetahui lebih lanjut strategi konflik yang tersedia, lihat dokumentasi.
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
Sekarang Room
akan membuat semua kode yang diperlukan untuk memasukkan item
ke dalam database. Saat Anda memanggil insert()
dari kode Kotlin, Room
akan menjalankan kueri SQL untuk memasukkan entity ke dalam database. (Catatan: Fungsi ini dapat diberi nama apa pun yang Anda inginkan, tetapi tidak harus disebut insert()
.)
- Tambahkan anotasi
@Update
dengan fungsiupdate()
untuk satuitem
. Entity yang diperbarui memiliki kunci yang sama dengan entity yang diteruskan. Anda dapat memperbarui beberapa atau semua properti lainnya dari entity tersebut. Serupa dengan metodeinsert()
, jadikan metodeupdate()
berikut sebagaisuspend
.
@Update
suspend fun update(item: Item)
- Tambahkan anotasi
@Delete
dengan fungsidelete()
untuk menghapus item. Jadikan sebagai metode penangguhan. Anotasi@Delete
menghapus satu item, atau sebuah daftar item. (Catatan: Anda harus meneruskan entity untuk dihapus. Jika tidak memiliki entity, Anda mungkin harus mengambilnya sebelum memanggil fungsidelete()
.)
@Delete
suspend fun delete(item: Item)
Tidak ada anotasi kemudahan untuk fungsi yang tersisa, sehingga Anda harus menggunakan anotasi @Query
dan menyediakan kueri SQLite.
- Tulis kueri SQLite untuk mengambil item tertentu dari tabel item berdasarkan
id
yang diberikan. Anda kemudian akan menambahkan anotasi Room dan menggunakan versi kueri berikut yang dimodifikasi pada langkah berikutnya. Di langkah berikutnya, Anda juga akan mengubahnya menjadi metode DAO menggunakan Room. - Pilih semua kolom dari
item
WHERE
id
cocok dengan nilai tertentu.
Contoh:
SELECT * from item WHERE id = 1
- Ubah kueri SQL di atas untuk digunakan oleh anotasi Room dan argumen. Tambahkan anotasi
@Query
, sediakan kueri sebagai parameter string ke anotasi@Query
. Tambahkan parameterString
ke@Query
yang merupakan kueri SQLite untuk mengambil item dari tabel item. - Pilih semua kolom dari
item
WHERE
id
cocok dengan argumenid
. Perhatikan:id
. Anda dapat menggunakan notasi titik dua di kueri untuk mereferensikan argumen dalam fungsi.
@Query("SELECT * from item WHERE id = :id")
- Di bawah anotasi
@Query
, tambahkan fungsigetItem()
yang menggunakan argumenInt
serta tampilkanFlow<Item>
.
@Query("SELECT * from item WHERE id = :id")
fun getItem(id: Int): Flow<Item>
Menggunakan Flow
atau LiveData
sebagai jenis nilai yang ditampilkan akan memastikan Anda mendapatkan notifikasi setiap kali data dalam database berubah. Sebaiknya gunakan Flow
di lapisan persistensi. Room
terus memperbarui Flow
ini, yang berarti Anda hanya perlu mendapatkan data secara eksplisit satu kali. Hal ini berguna untuk memperbarui daftar inventaris, yang akan Anda terapkan di codelab berikutnya. Karena jenis nilai yang ditampilkan Flow
, Room juga menjalankan kueri pada thread latar belakang. Anda tidak perlu membuatnya secara eksplisit sebagai fungsi suspend
dan memanggil di dalam cakupan coroutine.
Anda mungkin perlu mengimpor Flow
dari kotlinx.coroutines.flow.Flow
.
- Tambahkan
@Query
dengan fungsigetItems()
: - Minta kueri SQLite untuk menampilkan semua kolom dari tabel
item
yang diurutkan dalam urutan menaik. - Minta
getItems()
menampilkan daftar entityItem
sebagaiFlow
.Room
terus memperbaruiFlow
ini, yang berarti Anda hanya perlu mendapatkan data secara eksplisit satu kali.
@Query("SELECT * from item ORDER BY name ASC")
fun getItems(): Flow<List<Item>>
- Meskipun Anda tidak akan melihat perubahan apa pun yang terlihat, jalankan aplikasi untuk memastikan tidak ada error.
7. Membuat instance Database
Di tugas ini, Anda akan membuat RoomDatabase
dengan menggunakan Entity
dan DAO yang telah dibuat di tugas sebelumnya. Class database menentukan daftar entity dan objek akses data. Class database ini juga merupakan titik akses utama untuk koneksi dasar.
Class Database
memberikan instance DAO yang telah Anda tentukan untuk aplikasi. Selanjutnya, aplikasi dapat menggunakan DAO untuk mengambil data dari database sebagai instance dari objek entity data terkait. Aplikasi juga dapat menggunakan entity data yang ditentukan untuk memperbarui baris dari tabel yang sesuai atau membuat baris baru untuk penyisipan.
Anda perlu membuat class RoomDatabase
abstrak, yang dianotasi dengan @Database
. Class ini memiliki satu metode yang dapat membuat instance RoomDatabase
yang tidak ada, atau menampilkan instance RoomDatabase
yang sudah ada.
Berikut adalah proses umum untuk mendapatkan instance RoomDatabase
:
- Buat class
public abstract
yang memperluasRoomDatabase
. Class abstrak baru yang Anda tentukan berfungsi sebagai holder database. Class yang Anda tentukan bersifat abstrak, karenaRoom
yang akan membuatkan implementasi untuk Anda. - Anotasikan class dengan
@Database
. Dalam argumen, cantumkan entity untuk database dan tetapkan nomor versinya. - Tentukan metode atau properti abstrak yang menampilkan Instance
ItemDao
laluRoom
akan menghasilkan penerapan untuk Anda. - Anda hanya memerlukan satu instance
RoomDatabase
untuk seluruh aplikasi, sehingga jadikanRoomDatabase
sebuah singleton. - Gunakan
Room.databaseBuilder
Room
untuk membuat database (item_database
) hanya jika tidak ada. Jika tidak, tampilkan database yang ada.
Membuat Database
- Di paket
data
, buat class KotlinItemRoomDatabase.kt
. - Di file
ItemRoomDatabase.kt
, buat classItemRoomDatabase
sebagai classabstract
yang memperluasRoomDatabase
. Anotasikan class dengan@Database
. Anda akan memperbaiki error parameter yang hilang pada langkah berikutnya.
@Database
abstract class ItemRoomDatabase : RoomDatabase() {}
- Anotasi
@Database
memerlukan beberapa argumen, sehinggaRoom
dapat membuat database.
- Tentukan
Item
sebagai satu-satunya class dengan daftarentities
. - Setel
version
sebagai1
. Setiap kali mengubah skema tabel database, Anda harus meningkatkan nomor versinya. - Setel
exportSchema
kefalse
agar tidak menyimpan cadangan histori versi skema.
@Database(entities = [Item::class], version = 1, exportSchema = false)
- Database harus mengetahui DAO. Di dalam isi class, deklarasikan fungsi abstrak yang menampilkan
ItemDao
. Anda dapat memiliki beberapa DAO.
abstract fun itemDao(): ItemDao
- Di bawah fungsi abstrak, tentukan objek
companion
. Objek pendamping memungkinkan akses ke metode untuk membuat atau mendapatkan database dengan menggunakan nama class sebagai penentu.
companion object {}
- Di dalam objek
companion
, deklarasikan variabel nullable pribadiINSTANCE
untuk database lalu inisialisasikan kenull
. VariabelINSTANCE
akan menyimpan referensi ke database ketika salah satunya telah dibuat. Hal ini membantu mempertahankan satu instance dari database yang dibuka pada waktu tertentu, yang merupakan resource mahal untuk dibuat dan dikelola.
Anotasikan INSTANCE
dengan @Volatile
. Nilai variabel yang tidak stabil tidak akan disimpan dalam cache, dan semua penulisan dan pembacaan akan dilakukan ke dan dari memori utama. Hal tersebut akan membantu memastikan agar nilai INSTANCE
selalu terbaru dan sama untuk semua thread eksekusi. Ini berarti perubahan yang dibuat oleh satu thread ke INSTANCE
akan langsung terlihat oleh semua thread lainnya.
@Volatile
private var INSTANCE: ItemRoomDatabase? = null
- Di bawah
INSTANCE
, saat masih berada di dalam objekcompanion
, tentukan metodegetDatabase()
dengan parameterContext
yang diperlukan builder database. Tampilkan jenisItemRoomDatabase
. Anda akan melihat error karenagetDatabase()
belum menampilkan apa pun.
fun getDatabase(context: Context): ItemRoomDatabase {}
- Beberapa thread dapat berpotensi mengalami kondisi race dan meminta instance database secara bersamaan, sehingga menghasilkan dua database, bukan satu. Dengan menggabungkan kode untuk mendapatkan database di dalam blok
synchronized
berarti hanya satu thread eksekusi dalam satu waktu yang dapat memasukkan blok kode ini, yang memastikan bahwa database hanya diinisialisasi sekali.
Dalam getDatabase()
, tampilkan variabel INSTANCE
. Atau, jika INSTANCE
adalah null, lakukan inisialisasi di dalam blok synchronized{}
. Gunakan operator elvis (?:
) untuk melakukannya. Teruskan this
objek pendamping yang ingin dikunci di dalam blok fungsi. Anda akan memperbaiki error pada langkah berikutnya.
return INSTANCE ?: synchronized(this) { }
- Di dalam blok yang disinkronkan, buat variabel instance
val
, dan gunakan builder database untuk mendapatkan database. Anda masih akan mengalami error yang akan diperbaiki pada langkah berikutnya.
val instance = Room.databaseBuilder()
- Di akhir blok
synchronized
, tampilkaninstance
.
return instance
- Di dalam blok
synchronized
, lakukan inisialisasi variabelinstance
, dan gunakan builder database untuk mendapatkan database. Teruskan konteks aplikasi, class database, serta nama untuk database,item_database
keRoom.databaseBuilder()
.
val instance = Room.databaseBuilder(
context.applicationContext,
ItemRoomDatabase::class.java,
"item_database"
)
Android Studio akan menghasilkan error Ketidakcocokan Jenis. Untuk menghapus error ini, Anda harus menambahkan strategi migrasi dan build()
di langkah berikut.
- Tambahkan strategi migrasi yang diperlukan ke builder. Gunakan
.fallbackToDestructiveMigration()
.
Biasanya, Anda harus memberikan objek migrasi dengan strategi migrasi saat skema berubah. Objek migrasi adalah objek yang menentukan cara Anda mengambil semua baris dengan skema lama dan mengonversinya menjadi baris dalam skema yang baru, sehingga tidak ada data yang hilang. Migrasi berada di luar cakupan codelab ini. Solusi yang mudah adalah dengan menghancurkan dan membuat ulang database yang berarti data tersebut akan hilang.
.fallbackToDestructiveMigration()
- Untuk membuat instance database, panggil
.build()
. Tindakan ini akan menghapus error Android Studio.
.build()
- Di dalam blok
synchronized
, tetapkanINSTANCE = instance
.
INSTANCE = instance
- Di akhir blok
synchronized
, tampilkaninstance
. Kode final akan terlihat seperti ini:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class ItemRoomDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
@Volatile
private var INSTANCE: ItemRoomDatabase? = null
fun getDatabase(context: Context): ItemRoomDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
ItemRoomDatabase::class.java,
"item_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
}
- Buat kode untuk memastikan tidak ada error.
Mengimplementasikan class Application
Di tugas ini, Anda akan membuat instance database di class Application.
- Buka
InventoryApplication.kt
, buatval
bernamadatabase
dari jenisItemRoomDatabase
. Buat instancedatabase
dengan memanggilgetDatabase()
saatItemRoomDatabase
meneruskan dalam konteks. Gunakan delegasilazy
sehingga instancedatabase
akan lambat dibuat saat Anda pertama kali memerlukan/mengakses referensi (bukan saat aplikasi dimulai). Tindakan ini akan membuat database (database fisik pada disk) pada akses pertama.
import android.app.Application
import com.example.inventory.data.ItemRoomDatabase
class InventoryApplication : Application(){
val database: ItemRoomDatabase by lazy { ItemRoomDatabase.getDatabase(this) }
}
Anda akan menggunakan instance database
ini nanti di codelab saat membuat instance ViewModel.
Sekarang Anda memiliki semua elemen penyusun untuk menggunakan Room. Kode ini dikompilasi dan dijalankan, tetapi Anda tidak dapat mengetahui apakah kode tersebut benar-benar berfungsi. Jadi, ini adalah waktu yang tepat untuk menambahkan item baru ke database Inventory untuk menguji database Anda. Untuk melakukannya, Anda memerlukan ViewModel
agar dapat berkomunikasi dengan database.
8. Menambahkan ViewModel
Sejauh ini, Anda telah membuat database dan class UI merupakan bagian dari kode awal. Untuk menyimpan data sementara aplikasi dan juga mengakses database, Anda memerlukan ViewModel. ViewModel Inventory Anda akan berinteraksi dengan database melalui DAO serta memberikan data ke UI. Semua operasi database harus dijalankan dari UI thread utama dan Anda akan melakukannya menggunakan coroutine dan viewModelScope
.
Membuat ViewModel Inventory
- Di paket
com.example.inventory
, buat file class KotlinInventoryViewModel.kt
. - Perluas class
InventoryViewModel
dari classViewModel
. Teruskan objekItemDao
sebagai parameter ke konstruktor default.
class InventoryViewModel(private val itemDao: ItemDao) : ViewModel() {}
- Di akhir file
InventoryViewModel.kt
di luar class, tambahkan classInventoryViewModelFactory
untuk membuat instanceInventoryViewModel
. Teruskan parameter konstruktor yang sama sebagaiInventoryViewModel
yang merupakan instanceItemDao
. Perluas class dari classViewModelProvider.Factory
. Anda akan memperbaiki error terkait metode yang tidak diterapkan di langkah berikutnya.
class InventoryViewModelFactory(private val itemDao: ItemDao) : ViewModelProvider.Factory {
}
- Klik bohlam merah dan pilih Implement Members atau Anda dapat mengganti metode
create()
di dalam classViewModelProvider.Factory
sebagai berikut, yang mengambil jenis class apa pun sebagai argumen dan menampilkan objekViewModel
.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
TODO("Not yet implemented")
}
- Implementasikan metode
create()
. Periksa apakahmodelClass
sama dengan classInventoryViewModel
dan tampilkan instance-nya. Jika tidak, berikan pengecualian.
if (modelClass.isAssignableFrom(InventoryViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return InventoryViewModel(itemDao) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
Mengisi ViewModel
Di tugas ini, Anda akan mengisi class InventoryViewModel
untuk menambahkan data inventaris ke database. Amati entity Item
dan layar Add Item di aplikasi Inventory.
@Entity
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
@ColumnInfo(name = "name")
val itemName: String,
@ColumnInfo(name = "price")
val itemPrice: Double,
@ColumnInfo(name = "quantity")
val quantityInStock: Int
)
Anda memerlukan nama, harga, dan stok untuk item tertentu tersebut agar dapat menambahkan entity ke database. Selanjutnya di codelab, Anda akan menggunakan layar Add Item untuk mendapatkan detail ini dari pengguna. Dalam tugas saat ini, Anda menggunakan tiga string sebagai input untuk ViewModel, mengonversinya menjadi instance entity Item
, dan menyimpannya ke database menggunakan instance ItemDao
. Saatnya melakukan implementasi.
- Di class
InventoryViewModel
, tambahkan fungsiprivate
yang disebutinsertItem()
yang mengambil objekItem
dan menambahkan data ke database dengan cara yang tidak memblokir.
private fun insertItem(item: Item) {
}
- Untuk berinteraksi dengan database dari thread utama, mulai coroutine dan panggil metode DAO di dalamnya. Di dalam metode
insertItem()
, gunakanviewModelScope.launch
untuk memulai coroutine dalamViewModelScope
. Di dalam fungsi peluncuran, panggil fungsi penangguhaninsert()
padaitemDao
yang meneruskanitem
.ViewModelScope
adalah properti ekstensi ke classViewModel
yang otomatis membatalkan coroutine turunannya ketikaViewModel
dihancurkan.
private fun insertItem(item: Item) {
viewModelScope.launch {
itemDao.insert(item)
}
}
Impor kotlinx.coroutines.launch,
androidx.lifecycle.
viewModelScope
com.example.inventory.data.Item
, jika tidak diimpor secara otomatis.
- Dalam class
InventoryViewModel
, tambahkan fungsi pribadi lainnya yang menggunakan tiga string dan menampilkan instanceItem
.
private fun getNewItemEntry(itemName: String, itemPrice: String, itemCount: String): Item {
return Item(
itemName = itemName,
itemPrice = itemPrice.toDouble(),
quantityInStock = itemCount.toInt()
)
}
- Masih di dalam class
InventoryViewModel
, tambahkan fungsi publik bernamaaddNewItem()
yang menggunakan tiga string untuk detail item. Teruskan string detail item ke fungsigetNewItemEntry()
dan tetapkan nilai yang ditampilkan ke val yang bernamanewItem
. Lakukan panggilan keinsertItem()
dengan meneruskannewItem
untuk menambahkan entity baru ke database. Fungsi ini akan dipanggil dari fragmen UI untuk menambahkan detail Item ke database.
fun addNewItem(itemName: String, itemPrice: String, itemCount: String) {
val newItem = getNewItemEntry(itemName, itemPrice, itemCount)
insertItem(newItem)
}
Perlu diperhatikan bahwa Anda tidak menggunakan viewModelScope.launch
untuk addNewItem()
, tetapi hal tersebut diperlukan di atas dalam insertItem()
saat Anda memanggil metode DAO. Alasannya adalah fungsi penangguhan hanya boleh dipanggil dari coroutine atau fungsi penangguhan lainnya. Fungsi itemDao.insert(item)
merupakan fungsi penangguhan.
Anda telah menambahkan semua fungsi yang diperlukan untuk menambahkan entity ke database. Pada tugas berikutnya, Anda akan memperbarui fragmen Add Item untuk menggunakan fungsi di atas.
9. Mengupdate AddItemFragment
- Di
AddItemFragment.kt
, di awal classAddItemFragment
, buatprivate val
yang disebutviewModel
dari jenisInventoryViewModel
. Gunakan delegasi properti Kotlinby activityViewModels()
untuk membagikanViewModel
di seluruh fragmen. Anda akan memperbaiki error di langkah berikutnya.
private val viewModel: InventoryViewModel by activityViewModels {
}
- Di dalam lambda, panggil konstruktor
InventoryViewModelFactory()
lalu teruskan instanceItemDao
. Gunakan instancedatabase
yang Anda buat di salah satu tugas sebelumnya untuk memanggil konstruktoritemDao
.
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database
.itemDao()
)
}
- Di bawah definisi
viewModel
, buatlateinit var
yang disebutitem
dari jenisItem
.
lateinit var item: Item
- Layar Add Item berisi tiga kolom teks untuk mendapatkan detail item dari pengguna. Di langkah ini, Anda akan menambahkan fungsi untuk memverifikasi apakah teks di TextFields tidak kosong. Anda akan menggunakan fungsi ini untuk memverifikasi input pengguna sebelum menambah atau memperbarui entity dalam database. Validasi ini perlu dilakukan di
ViewModel
dan bukan di Fragment. Di classInventoryViewModel
, tambahkan fungsipublic
berikut yang disebutisEntryValid()
.
fun isEntryValid(itemName: String, itemPrice: String, itemCount: String): Boolean {
if (itemName.isBlank() || itemPrice.isBlank() || itemCount.isBlank()) {
return false
}
return true
}
- Di
AddItemFragment.kt
, di bawah fungsionCreateView()
, buat fungsiprivate
yang disebutisEntryValid()
yang menampilkanBoolean
. Anda akan memperbaiki error "nilai yang ditampilkan tidak ada" di langkah berikutnya.
private fun isEntryValid(): Boolean {
}
- Di class
AddItemFragment
, implementasikan fungsiisEntryValid()
. Panggil fungsiisEntryValid()
pada instanceviewModel
, yang meneruskan teks dari tampilan teks. Tampilkan nilai fungsiviewModel.isEntryValid()
.
private fun isEntryValid(): Boolean {
return viewModel.isEntryValid(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString()
)
}
- Di class
AddItemFragment
di bawah fungsiisEntryValid()
, tambahkan fungsiprivate
lain yang disebutaddNewItem()
tanpa parameter serta tidak menampilkan apa pun. Di dalam fungsi, panggilisEntryValid()
di dalam kondisiif
.
private fun addNewItem() {
if (isEntryValid()) {
}
}
- Di dalam blok
if
, panggil metodeaddNewItem()
pada instanceviewModel
. Teruskan detail item yang dimasukkan oleh pengguna, gunakan instancebinding
untuk membacanya.
if (isEntryValid()) {
viewModel.addNewItem(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString(),
)
}
- Di bawah blok
if
, buatval
action
untuk kembali keItemListFragment
. PanggilfindNavController
().navigate()
, dengan meneruskanaction
.
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)
Impor androidx.navigation.fragment.findNavController.
- Metode lengkapnya akan terlihat seperti berikut.
private fun addNewItem() {
if (isEntryValid()) {
viewModel.addNewItem(
binding.itemName.text.toString(),
binding.itemPrice.text.toString(),
binding.itemCount.text.toString(),
)
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)
}
}
- Untuk menggabungkan semuanya, tambahkan pengendali klik ke tombol Save. Di class
AddItemFragment
, di atas fungsionDestroyView()
, ganti fungsionViewCreated()
. - Di dalam fungsi
onViewCreated()
, tambahkan pengendali klik ke tombol save, dan panggiladdNewItem()
dari fungsi tersebut.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.saveAction.setOnClickListener {
addNewItem()
}
}
- Build dan jalankan aplikasi Anda. Ketuk ikon + Fab. Di layar Add Item, tambahkan detail item dan ketuk Save. Tindakan ini menyimpan data, tetapi Anda belum dapat melihat apa pun di aplikasi. Di tugas berikutnya, Anda akan menggunakan Database Inspector untuk melihat data yang disimpan.
Melihat database menggunakan Database Inspector
- Jalankan aplikasi Anda di emulator atau perangkat terhubung yang menjalankan API level 26 atau lebih tinggi, jika Anda belum melakukannya. Database Inspector berfungsi paling baik pada emulator/perangkat yang menjalankan API level 26.
- Di Android Studio, pilih View > Tool Windows > Database Inspector dari panel menu.
- Di panel Database Inspector, pilih
com.example.inventory
dari menu dropdown. - item_database di aplikasi Inventory akan muncul di panel Databases. Perluas node untuk item_database dan pilih Item untuk diperiksa. Jika panel Databases Anda kosong, gunakan emulator untuk menambahkan beberapa item ke database menggunakan layar Add Item.
- Centang kotak Live updates di Database Inspector untuk otomatis memperbarui data yang ditampilkan saat Anda berinteraksi dengan aplikasi yang berjalan di emulator atau perangkat.
Selamat! Anda telah membuat aplikasi yang dapat menyimpan data dengan menggunakan Room. Pada codelab berikutnya, Anda akan menambahkan RecyclerView
ke aplikasi untuk menampilkan item pada database serta menambahkan fitur baru ke aplikasi seperti menghapus dan memperbarui entity. Sampai jumpa!
10. Kode solusi
Kode solusi untuk codelab ini ada di repo dan cabang GitHub yang ditampilkan di bawah.
Untuk mendapatkan kode codelab ini dan membukanya di Android Studio, lakukan hal berikut.
Mendapatkan kode
- Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
- Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.
- Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
- Temukan file di komputer Anda (mungkin di folder Downloads).
- Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.
Membuka project di Android Studio
- Mulai Android Studio.
- Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.
Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.
- Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
- Klik dua kali pada folder project tersebut.
- Tunggu Android Studio membuka project.
- Klik tombol Run untuk mem-build dan menjalankan aplikasi. Pastikan aplikasi di-build seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi disiapkan.
11. Ringkasan
- Tentukan tabel Anda sebagai class data yang dianotasi dengan
@Entity
. Tentukan properti yang dianotasi dengan@ColumnInfo
sebagai kolom dalam tabel. - Tentukan objek akses data (DAO) sebagai antarmuka yang dianotasi dengan
@Dao
. DAO memetakan fungsi Kotlin ke kueri database. - Gunakan anotasi untuk menentukan fungsi
@Insert
,@Delete
, dan@Update
. - Gunakan anotasi
@Query
dengan string kueri SQLite sebagai parameter untuk kueri lainnya. - Gunakan Database Inspector untuk melihat data yang disimpan di database Android SQLite.
12. Mempelajari lebih lanjut
Dokumentasi Developer Android
- Menyimpan data di dalam database lokal menggunakan Room
- androidx.room
- Mendebug database dengan Database Inspector
Postingan blog
Video
Dokumentasi dan artikel lainnya