1. Sebelum memulai
Sebagian besar aplikasi berkualitas produksi memiliki data yang harus dipertahankan oleh aplikasi. Misalnya, aplikasi dapat menyimpan playlist lagu, item dalam daftar tugas, catatan pengeluaran dan pendapatan, katalog konstelasi, atau histori data pribadi. Untuk kasus penggunaan semacam itu, Anda menggunakan database guna 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. Dibandingkan menggunakan SQLite secara langsung, Room lebih menyederhanakan tugas-tugas penyiapan, konfigurasi, dan interaksi database dengan aplikasi. Room juga menyediakan pemeriksaan waktu kompilasi terhadap pernyataan SQLite.
Lapisan abstraksi adalah sekumpulan fungsi yang menyembunyikan implementasi/kompleksitas yang mendasarinya. Fungsi ini menyediakan antarmuka ke kumpulan fungsi yang ada, seperti SQLite dalam hal ini.
Gambar di bawah menunjukkan kesesuaian Room sebagai sumber data dengan arsitektur keseluruhan yang direkomendasikan dalam kursus ini. Room adalah Sumber Data.

Prasyarat
- Kemampuan untuk membangun antarmuka pengguna (UI) dasar untuk aplikasi Android menggunakan Jetpack Compose.
- Kemampuan untuk menggunakan composable seperti
Text,Icon,IconButton, danLazyColumn. - Kemampuan untuk menggunakan composable
NavHostuntuk menentukan rute dan layar di aplikasi Anda. - Kemampuan menavigasi antarlayar menggunakan
NavHostController. - Pemahaman tentang komponen arsitektur Android
ViewModel. Kemampuan menggunakanViewModelProvider.Factoryuntuk membuat instance ViewModels. - Pemahaman tentang dasar-dasar konkurensi.
- Kemampuan menggunakan coroutine untuk tugas yang berjalan lama.
- Pengetahuan dasar tentang database SQLite dan bahasa SQL.
Yang akan Anda pelajari
- Cara membuat dan berinteraksi dengan database SQLite menggunakan library Room.
- Cara membuat entity, objek akses data (DAO), dan class database.
- Cara menggunakan DAO untuk memetakan fungsi Kotlin ke kueri SQL.
Yang akan Anda bangun
- Anda akan membangun aplikasi Inventory yang menyimpan item inventaris ke dalam database SQLite.
Yang Anda perlukan
- Kode awal untuk aplikasi Inventory
- Komputer dengan Android Studio
- Perangkat atau emulator dengan API level 26 atau yang lebih tinggi
2. Ringkasan aplikasi
Dalam codelab ini, Anda akan menggunakan kode awal aplikasi Inventory dan menambahkan lapisan database ke dalamnya menggunakan library Room. Versi final aplikasi menampilkan daftar item dari database inventaris. Pengguna memiliki opsi untuk menambahkan item baru, mengupdate item yang ada, dan menghapus item dari database inventaris. Untuk codelab ini, Anda menyimpan data item ke database Room. Anda akan menyelesaikan fungsi aplikasi lainnya di codelab berikutnya.
|
|
|
3. Ringkasan aplikasi awal
Mendownload kode awal untuk codelab ini
Untuk memulai, download kode awal:
Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app.git $ cd basic-android-kotlin-compose-training-inventory-app $ git checkout starter
Anda dapat menjelajahi kode di repositori GitHub Inventory app.
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 berjalan dengan API level 26 atau lebih tinggi. Database Inspector berfungsi di emulator/perangkat yang menjalankan API level 26 dan versi lebih baru.
- Perhatikan bahwa aplikasi tidak menampilkan data inventaris.
- Ketuk tombol tindakan mengambang (FAB), yang memungkinkan Anda menambahkan item baru ke database.
Aplikasi akan membuka layar baru tempat Anda dapat memasukkan detail item baru.
|
|
Masalah dengan kode awal
- Di layar Add Item, masukkan detail item seperti nama, harga, dan jumlah Item.
- Ketuk Simpan. Layar Add Item tidak ditutup, tetapi Anda dapat kembali menggunakan tombol kembali. Fungsi simpan tidak diterapkan sehingga detail item tidak disimpan.
Perhatikan bahwa aplikasi tidak lengkap dan fungsi tombol Save tidak diterapkan.

Dalam codelab ini, Anda akan menambahkan kode yang menggunakan Room untuk menyimpan detail inventaris di database SQLite. Anda menggunakan library persistensi Room untuk berinteraksi dengan database SQLite.
Panduan kode
Kode awal yang Anda download memiliki tata letak layar yang telah didesain sebelumnya untuk Anda. Di jalur ini, Anda akan berfokus untuk menerapkan logika database. Bagian berikut adalah panduan singkat beberapa file untuk membantu Anda memulai.
ui/home/HomeScreen.kt
File ini adalah layar utama, atau layar pertama di aplikasi, yang berisi composable untuk menampilkan daftar inventaris. File ini memiliki FAB
untuk menambahkan item baru ke daftar. Anda nanti akan menampilkan item dalam daftar di jalur tersebut.

ui/item/ItemEntryScreen.kt
Layar ini mirip dengan ItemEditScreen.kt. Keduanya memiliki kolom teks untuk detail item. Layar ini ditampilkan saat FAB diketuk di layar utama. ItemEntryViewModel.kt adalah ViewModel yang sesuai untuk layar ini.

ui/navigation/InventoryNavGraph.kt
File ini adalah grafik navigasi untuk seluruh aplikasi.
4. Komponen utama Room
Kotlin menyediakan cara mudah untuk menangani data melalui class data. Meskipun mudah untuk bekerja dengan data dalam memori menggunakan class data, untuk mempertahankan data, Anda perlu mengonversi data ini menjadi format yang kompatibel dengan penyimpanan database. Untuk melakukannya, Anda memerlukan tabel untuk menyimpan data dan kueri untuk mengakses dan mengubah data.
Tiga komponen Room berikut membuat alur kerja ini menjadi lancar.
- Entity Room menampilkan tabel di database aplikasi Anda. Anda menggunakannya untuk memperbarui data yang disimpan dalam baris di tabel dan membuat baris baru untuk penyisipan.
- DAOs Room menyediakan metode yang digunakan aplikasi Anda untuk mengambil, memperbarui, menyisipkan, dan menghapus data dalam database.
- Class database Room adalah class database yang menyediakan instance DAO yang terkait dengan database tersebut ke aplikasi Anda.
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 dependensi Room
Dalam tugas ini, Anda akan menambahkan library komponen Room yang diperlukan ke file Gradle Anda.
- Buka file gradle level modul
build.gradle.kts (Module: InventoryApp.app). - Di blok
dependencies, tambahkan dependensi untuk library Room yang ditampilkan dalam kode berikut.
//Room
implementation("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
ksp("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
implementation("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
KSP adalah API yang canggih tetapi sederhana untuk mengurai anotasi Kotlin.
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 menyimpan informasi tentang item inventaris, seperti nama item, harga item, dan jumlah item yang tersedia.

Anotasi @Entity menandai class sebagai class Entity database. Untuk setiap class Entity, aplikasi membuat tabel database untuk menyimpan 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 aplikasi menetapkan kunci utama, hal itu tidak dapat diubah. Kunci utama merepresentasikan objek entity selama kunci utama itu berada dalam database.
Dalam tugas ini, Anda membuat class Entity dan menentukan kolom untuk menyimpan informasi inventaris berikut untuk setiap item: Int untuk menyimpan kunci utama, String untuk menyimpan nama item, double untuk menyimpan harga item, dan Int untuk menyimpan jumlah yang tersedia.
- Buka kode awal di Android Studio.
- Buka paket
datapada paket dasarcom.example.inventory. - Di dalam paket
data, buka class KotlinItemyang mewakili entity database di aplikasi Anda.
// No need to copy over, this is part of the starter code
class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
Class data
Class data utamanya digunakan untuk menyimpan data di Kotlin. Class data ini ditentukan dengan kata kunci data. Objek class data Kotlin memiliki beberapa manfaat tambahan. Misalnya, compiler otomatis membuat utility untuk membandingkan, mencetak, dan menyalin, seperti toString(), copy(), dan equals().
Contoh:
// Example data class with 2 properties.
data class User(val firstName: String, val lastName: 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 berupa
valatauvar. - Class data tidak boleh berupa
abstract,open, atausealed.
Untuk mempelajari class Data lebih lanjut, lihat dokumentasi Class data.
- Awali definisi class
Itemdengan kata kuncidatauntuk mengonversinya menjadi class data.
data class Item(
val id: Int,
val name: String,
val price: Double,
val quantity: Int
)
- Di atas deklarasi class
Item, beri anotasi pada class data dengan@Entity. Gunakan argumentableNameuntuk menetapkanitemssebagai nama tabel SQLite.
import androidx.room.Entity
@Entity(tableName = "items")
data class Item(
...
)
- Anotasikan properti
iddengan@PrimaryKeyuntuk menjadikanidsebagai kunci utama. Kunci utama adalah ID untuk mengidentifikasi setiap catatan/entri dalam tabelItemAnda secara unik
import androidx.room.PrimaryKey
@Entity(tableName = "items")
data class Item(
@PrimaryKey
val id: Int,
...
)
- Tetapkan nilai default sebesar
0padaid, yang diperlukaniduntuk membuat nilaiidsecara otomatis. - Tambahkan parameter
autoGenerateke anotasi@PrimaryKeyuntuk menentukan apakah kolom kunci utama harus dibuat secara otomatis. JikaautoGeneratedisetel ketrue, Room akan otomatis membuat nilai unik untuk kolom kunci utama saat instance entity baru dimasukkan ke dalam database. Ini memastikan bahwa setiap instance entity memiliki ID unik, tanpa harus menetapkan nilai ke kolom kunci utama secara manual
data class Item(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
// ...
)
Bagus. Setelah membuat class Entity, Anda dapat membuat Objek Akses Data (DAO) untuk mengakses database.
6. Membuat DAO item
Objek Akses Data (DAO) adalah pola yang dapat Anda gunakan untuk memisahkan lapisan persistensi dari 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 terjadi ketika menjalankan operasi database dalam lapisan persistensi yang mendasari, terpisah dari bagian aplikasi lainnya. Hal ini memungkinkan Anda mengubah lapisan data secara terpisah dari kode yang menggunakan data.

Dalam tugas ini, Anda akan menentukan DAO untuk Room. DAO adalah komponen utama Room yang bertanggung jawab untuk menentukan antarmuka yang mengakses database.
DAO yang Anda buat adalah antarmuka kustom yang menyediakan metode praktis untuk melakukan kueri/mengambil, memasukkan, menghapus, dan memperbarui database. Room menghasilkan implementasi dari class ini pada waktu kompilasi.
Library Room menyediakan anotasi kemudahan, seperti @Insert, @Delete, dan @Update, untuk menentukan metode yang menjalankan penyisipan, penghapusan, dan pembaruan sederhana tanpa mengharuskan Anda menulis pernyataan SQL.
Jika Anda perlu menentukan operasi yang lebih kompleks untuk menyisipkan, menghapus, memperbarui, atau jika perlu membuat kueri data dalam database, gunakan anotasi @Query.
Sebagai bonus tambahan, saat Anda menulis kueri di Android Studio, compiler akan memeriksa kueri SQL untuk menemukan error sintaksis.
Untuk aplikasi Inventaris, Anda memerlukan kemampuan untuk melakukan hal berikut:
- Sisipkan atau tambahkan item baru.
- Update 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.

Selesaikan langkah-langkah berikut untuk menerapkan DAO item di aplikasi Anda:
- Di paket
data, buat antarmuka KotlinItemDao.kt.

- Anotasi antarmuka
ItemDaodengan@Dao.
import androidx.room.Dao
@Dao
interface ItemDao {
}
- Di dalam isi antarmuka, tambahkan anotasi
@Insert. - Di bawah
@Insert, tambahkan fungsiinsert()yang menggunakan instance classEntityitemsebagai argumennya. - Tandai fungsi dengan kata kunci
suspendagar dapat berjalan di thread terpisah.
Operasi database dapat memerlukan waktu lama untuk dijalankan sehingga harus berjalan di thread terpisah. Room tidak mengizinkan akses database pada thread utama.
import androidx.room.Insert
@Insert
suspend fun insert(item: Item)
Ketika menyisipkan item ke dalam database, konflik dapat terjadi. Misalnya, beberapa tempat dalam kode mencoba memperbarui entity dengan nilai yang berbeda dan bertentangan, seperti kunci utama yang sama. Entity merupakan baris di DB. Di aplikasi Inventory, kita hanya menyisipkan entity dari satu tempat yang merupakan layar Add Item sehingga tidak terjadi konflik dan kita dapat menetapkan strategi konflik ke Ignore.
- Tambahkan argumen
onConflictdan tetapkan nilaiOnConflictStrategy.IGNORE.
Argumen onConflict memberi tahu Room apa yang harus dilakukan jika terjadi konflik. Strategi OnConflictStrategy.IGNORE mengabaikan item baru.
Untuk mengetahui lebih lanjut strategi konflik yang tersedia, lihat dokumentasiOnConflictStrategy.
import androidx.room.OnConflictStrategy
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
Sekarang Room menghasilkan semua kode yang diperlukan untuk memasukkan item ke dalam database. Saat Anda memanggil salah satu fungsi DAO yang ditandai dengan anotasi Room, Room akan mengeksekusi kueri SQL terkait pada database. Misalnya, saat Anda memanggil metode di atas, insert() dari kode Kotlin Anda, Room akan mengeksekusi kueri SQL untuk memasukkan entity ke dalam database.
- Tambahkan fungsi baru dengan anotasi
@Updateyang menggunakanItemsebagai parameter.
Entity yang diperbarui memiliki kunci utama yang sama dengan entity yang diteruskan. Anda dapat memperbarui beberapa atau semua properti lainnya dari entity tersebut.
- Serupa dengan metode
insert(), tandai fungsi ini dengan kata kuncisuspend.
import androidx.room.Update
@Update
suspend fun update(item: Item)
Tambahkan fungsi lain dengan anotasi @Delete untuk menghapus item, dan jadikan sebagai fungsi penangguhan.
import androidx.room.Delete
@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
idyang diberikan. Kode berikut memberikan contoh kueri yang memilih semua kolom dariitems, denganidyang cocok dengan nilai tertentu danidadalah ID unik.
Contoh:
// Example, no need to copy over
SELECT * from items WHERE id = 1
- Tambahkan anotasi
@Query. - Gunakan kueri SQLite dari langkah sebelumnya sebagai parameter string ke anotasi
@Query. - Tambahkan parameter
Stringke@Queryyang merupakan kueri SQLite untuk mengambil item dari tabel item.
Kueri sekarang meminta untuk memilih semua kolom dari items, dengan id yang cocok dengan argumen id. Perhatikan bahwa :id menggunakan notasi titik dua di kueri untuk mereferensikan argumen dalam fungsi.
@Query("SELECT * from items WHERE id = :id")
- Setelah anotasi
@Query, tambahkan fungsigetItem()yang menggunakan argumenIntdan menampilkanFlow<Item>.
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
Sebaiknya gunakan Flow di layer persistensi. Dengan Flow sebagai jenis nilai yang ditampilkan, Anda akan menerima notifikasi setiap kali data dalam database berubah. Room terus memperbarui Flow ini, yang berarti Anda hanya perlu mendapatkan data secara eksplisit satu kali. Penyiapan ini berguna untuk memperbarui daftar inventaris, yang 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 memanggilnya di dalam cakupan coroutine.
- Tambahkan
@Querydengan fungsigetAllItems(). - Minta kueri SQLite untuk menampilkan semua kolom dari tabel
itemyang diurutkan dalam urutan menaik. - Minta
getAllItems()menampilkan daftar entityItemsebagaiFlow.Roomterus memperbaruiFlowini, yang berarti Anda hanya perlu mendapatkan data secara eksplisit satu kali.
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
Menyelesaikan ItemDao:
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
interface ItemDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(item: Item)
@Update
suspend fun update(item: Item)
@Delete
suspend fun delete(item: Item)
@Query("SELECT * from items WHERE id = :id")
fun getItem(id: Int): Flow<Item>
@Query("SELECT * from items ORDER BY name ASC")
fun getAllItems(): Flow<List<Item>>
}
- Meskipun Anda tidak akan melihat perubahan apa pun yang terlihat, bangun aplikasi Anda untuk memastikan tidak ada error.
7. Membuat instance Database
Dalam tugas ini, Anda akan membuat RoomDatabase yang menggunakan Entity dan DAO dari tugas sebelumnya. Class database menentukan daftar entity dan DAO.
Class Database memberikan instance DAO yang 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 dan menganotasinya dengan @Database. Class ini memiliki satu metode yang menampilkan instance RoomDatabase yang ada jika database tidak ada.
Berikut adalah proses umum untuk mendapatkan instance RoomDatabase:
- Buat class
public abstractyang memperluasRoomDatabase. Class abstrak baru yang Anda tentukan berfungsi sebagai holder database. Class yang Anda tentukan bersifat abstrak, karenaRoomyang 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, danRoomakan menghasilkan implementasinya untuk Anda. - Anda hanya memerlukan satu instance
RoomDatabaseuntuk seluruh aplikasi, sehingga jadikanRoomDatabasesebuah singleton. - Gunakan
Room.databaseBuilderRoomuntuk membuat database (item_database) hanya jika tidak ada. Jika tidak, tampilkan database yang ada.
Membuat Database
- Di paket
data, buat class KotlinInventoryDatabase.kt. - Di file
InventoryDatabase.kt, buat classInventoryDatabasesebagai classabstractyang memperluasRoomDatabase. - Anotasikan class dengan
@Database. Abaikan error parameter yang tidak ada, yang akan Anda perbaiki di langkah berikutnya.
import androidx.room.Database
import androidx.room.RoomDatabase
@Database
abstract class InventoryDatabase : RoomDatabase() {}
Anotasi @Database memerlukan beberapa argumen sehingga Room dapat membuat database.
- Tentukan
Itemsebagai satu-satunya class dengan daftarentities. - Setel
versionsebagai1. Setiap kali mengubah skema tabel database, Anda harus meningkatkan nomor versinya. - Setel
exportSchemakefalseagar tidak menyimpan cadangan histori versi skema.
@Database(entities = [Item::class], version = 1, exportSchema = false)
- Di dalam isi class, deklarasikan fungsi abstrak yang menampilkan
ItemDaosehingga database mengetahui DAO.
abstract fun itemDao(): ItemDao
- Di bawah fungsi abstrak, tentukan
companion object, yang memungkinkan akses ke metode untuk membuat atau mendapatkan database dan menggunakan nama class sebagai penentu.
companion object {}
- Di dalam objek
companion, deklarasikan variabel nullable pribadiInstanceuntuk database lalu inisialisasikan kenull.
Variabel Instance 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
Instancedengan@Volatile.
Nilai variabel yang tidak stabil tidak pernah disimpan dalam cache, dan semua pembacaan dan penulisan dilakukan ke dan dari memori utama. Fitur ini membantu memastikan nilai Instance selalu yang terbaru dan sama untuk semua thread eksekusi. Hal ini berarti perubahan yang dibuat oleh satu thread ke Instance akan langsung terlihat oleh semua thread lainnya.
@Volatile
private var Instance: InventoryDatabase? = null
- Di bawah
Instance, saat masih berada di dalam objekcompanion, tentukan metodegetDatabase()dengan parameterContextyang diperlukan builder database. - Tampilkan jenis
InventoryDatabase. Pesan error muncul karenagetDatabase()belum menampilkan apa pun.
import android.content.Context
fun getDatabase(context: Context): InventoryDatabase {}
Beberapa thread dapat berpotensi meminta instance database secara bersamaan sehingga menghasilkan dua database, bukan satu. Masalah ini dikenal sebagai kondisi race. 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. Gunakan blok synchronized{} untuk menghindari kondisi race.
- Dalam
getDatabase(), tampilkan variabelInstance. Atau, jikaInstanceadalah null, lakukan inisialisasi di dalam bloksynchronized{}. Gunakan operator elvis (?:) untuk melakukannya. - Teruskan
this, objek pendamping. Anda dapat memperbaiki error tersebut di langkah berikutnya.
return Instance ?: synchronized(this) { }
- Di dalam blok yang disinkronkan, gunakan builder database untuk mendapatkan database. Lanjutkan untuk mengabaikan error, yang akan Anda perbaiki di langkah berikutnya.
import androidx.room.Room
Room.databaseBuilder()
- Di dalam blok
synchronized, gunakan builder database untuk mendapatkan database. Teruskan konteks aplikasi, class database, serta nama untuk database-item_databasekeRoom.databaseBuilder().
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
Android Studio menghasilkan error Ketidakcocokan Jenis. Untuk menghapus error ini, Anda harus menambahkan build() di langkah berikut.
- Tambahkan strategi migrasi yang diperlukan ke builder. Gunakan
.fallbackToDestructiveMigration().
.fallbackToDestructiveMigration()
- Untuk membuat instance database, panggil
.build(). Panggilan ini akan menghapus error Android Studio.
.build()
- Setelah
build(), tambahkan blokalsodan tetapkanInstance = ituntuk mempertahankan referensi ke instance db yang baru dibuat.
.also { Instance = it }
- Di akhir blok
synchronized, tampilkaninstance. Kode akhir Anda akan terlihat seperti kode berikut:
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
* Database class with a singleton Instance object.
*/
@Database(entities = [Item::class], version = 1, exportSchema = false)
abstract class InventoryDatabase : RoomDatabase() {
abstract fun itemDao(): ItemDao
companion object {
@Volatile
private var Instance: InventoryDatabase? = null
fun getDatabase(context: Context): InventoryDatabase {
// if the Instance is not null, return it, otherwise create a new database instance.
return Instance ?: synchronized(this) {
Room.databaseBuilder(context, InventoryDatabase::class.java, "item_database")
.build()
.also { Instance = it }
}
}
}
}
- Buat kode untuk memastikan tidak ada error.
8. Mengimplementasikan Repositori
Dalam tugas ini, Anda akan mengimplementasikan antarmuka ItemsRepository dan class OfflineItemsRepository untuk menyediakan entity get, insert, delete, dan update dari database.
- Buka file
ItemsRepository.ktpada paketdata. - Tambahkan fungsi berikut ke antarmuka, yang memetakan ke implementasi DAO.
import kotlinx.coroutines.flow.Flow
/**
* Repository that provides insert, update, delete, and retrieve of [Item] from a given data source.
*/
interface ItemsRepository {
/**
* Retrieve all the items from the the given data source.
*/
fun getAllItemsStream(): Flow<List<Item>>
/**
* Retrieve an item from the given data source that matches with the [id].
*/
fun getItemStream(id: Int): Flow<Item?>
/**
* Insert item in the data source
*/
suspend fun insertItem(item: Item)
/**
* Delete item from the data source
*/
suspend fun deleteItem(item: Item)
/**
* Update item in the data source
*/
suspend fun updateItem(item: Item)
}
- Buka file
OfflineItemsRepository.ktpada paketdata. - Teruskan parameter konstruktor jenis
ItemDao.
class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository
- Di class
OfflineItemsRepository, ganti fungsi yang ditentukan di antarmukaItemsRepositorydan panggil fungsi yang sesuai dariItemDao.
import kotlinx.coroutines.flow.Flow
class OfflineItemsRepository(private val itemDao: ItemDao) : ItemsRepository {
override fun getAllItemsStream(): Flow<List<Item>> = itemDao.getAllItems()
override fun getItemStream(id: Int): Flow<Item?> = itemDao.getItem(id)
override suspend fun insertItem(item: Item) = itemDao.insert(item)
override suspend fun deleteItem(item: Item) = itemDao.delete(item)
override suspend fun updateItem(item: Item) = itemDao.update(item)
}
Mengimplementasikan class AppContainer
Di tugas ini, Anda akan membuat instance database dan meneruskan instance DAO ke class OfflineItemsRepository.
- Buka file
AppContainer.ktpada paketdata. - Teruskan instance
ItemDao()ke konstruktorOfflineItemsRepository. - Buat instance database dengan memanggil
getDatabase()pada classInventoryDatabaseyang meneruskan konteks, lalu memanggil.itemDao()untuk membuat instanceDao.
override val itemsRepository: ItemsRepository by lazy {
OfflineItemsRepository(InventoryDatabase.getDatabase(context).itemDao())
}
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 saat yang tepat untuk menguji database Anda. Untuk menyelesaikan pengujian, Anda memerlukan ViewModel agar dapat berkomunikasi dengan database.
9. Menambahkan fungsi simpan
Sejauh ini Anda telah membuat database, dan class UI merupakan bagian dari kode awal. Untuk menyimpan data sementara aplikasi dan juga mengakses database, Anda perlu mengupdate ViewModel. ViewModel berinteraksi dengan database melalui DAO dan memberikan data ke UI. Semua operasi database harus dijalankan dari UI thread utama. Anda akan melakukannya dengan coroutine dan viewModelScope.
Panduan class status UI
Buka file ui/item/ItemEntryViewModel.kt. Class data ItemUiState mewakili status UI Item. Class data ItemDetails mewakili satu item.
Kode awal memberi Anda tiga fungsi ekstensi:
- Fungsi ekstensi
ItemDetails.toItem()mengonversi objek status UIItemUiStatemenjadi jenis entityItem. - Fungsi ekstensi
Item.toItemUiState()mengonversi objek entity RoomItemmenjadi jenis status UIItemUiState. - Fungsi ekstensi
Item.toItemDetails()mengonversi objek entity RoomItemmenjadiItemDetails.
// No need to copy, this is part of starter code
/**
* Represents Ui State for an Item.
*/
data class ItemUiState(
val itemDetails: ItemDetails = ItemDetails(),
val isEntryValid: Boolean = false
)
data class ItemDetails(
val id: Int = 0,
val name: String = "",
val price: String = "",
val quantity: String = "",
)
/**
* Extension function to convert [ItemDetails] to [Item]. If the value of [ItemDetails.price] is
* not a valid [Double], then the price will be set to 0.0. Similarly if the value of
* [ItemDetails.quantity] is not a valid [Int], then the quantity will be set to 0
*/
fun ItemDetails.toItem(): Item = Item(
id = id,
name = name,
price = price.toDoubleOrNull() ?: 0.0,
quantity = quantity.toIntOrNull() ?: 0
)
fun Item.formatedPrice(): String {
return NumberFormat.getCurrencyInstance().format(price)
}
/**
* Extension function to convert [Item] to [ItemUiState]
*/
fun Item.toItemUiState(isEntryValid: Boolean = false): ItemUiState = ItemUiState(
itemDetails = this.toItemDetails(),
isEntryValid = isEntryValid
)
/**
* Extension function to convert [Item] to [ItemDetails]
*/
fun Item.toItemDetails(): ItemDetails = ItemDetails(
id = id,
name = name,
price = price.toString(),
quantity = quantity.toString()
)
Anda akan menggunakan class di atas dalam model tampilan untuk membaca dan mengupdate UI.
Mengupdate ViewModel ItemEntry
Dalam tugas ini, Anda meneruskan repositori ke file ItemEntryViewModel.kt. Anda juga menyimpan detail item yang dimasukkan di layar Add Item ke dalam database.
- Perhatikan fungsi pribadi
validateInput()di classItemEntryViewModel.
// No need to copy over, this is part of starter code
private fun validateInput(uiState: ItemDetails = itemUiState.itemDetails): Boolean {
return with(uiState) {
name.isNotBlank() && price.isNotBlank() && quantity.isNotBlank()
}
}
Fungsi di atas memeriksa apakah name, price, dan quantity kosong. Anda akan menggunakan fungsi ini untuk memverifikasi input pengguna sebelum menambah atau memperbarui entity dalam database.
- Buka class
ItemEntryViewModeldan tambahkan parameter konstruktor defaultprivatedari jenisItemsRepository.
import com.example.inventory.data.ItemsRepository
class ItemEntryViewModel(private val itemsRepository: ItemsRepository) : ViewModel() {
}
- Update
initializeruntuk model tampilan entri item diui/AppViewModelProvider.ktdan teruskan instance repositori sebagai parameter.
object AppViewModelProvider {
val Factory = viewModelFactory {
// Other Initializers
// Initializer for ItemEntryViewModel
initializer {
ItemEntryViewModel(inventoryApplication().container.itemsRepository)
}
//...
}
}
- Buka file
ItemEntryViewModel.ktdan di akhir classItemEntryViewModel, lalu tambahkan fungsi penangguhan bernamasaveItem()untuk menyisipkan item ke dalam database Room. Fungsi ini menambahkan data ke database dengan cara yang tidak memblokir.
suspend fun saveItem() {
}
- Di dalam fungsi, periksa apakah
itemUiStatevalid, lalu konversikan ke jenisItemagar Room dapat memahami data. - Panggil
insertItem()padaitemsRepositorydan teruskan data. UI memanggil fungsi ini untuk menambahkan detail Item ke database.
suspend fun saveItem() {
if (validateInput()) {
itemsRepository.insertItem(itemUiState.itemDetails.toItem())
}
}
Anda telah menambahkan semua fungsi yang diperlukan untuk menambahkan entity ke database. Pada tugas berikutnya, Anda akan mengupdate UI untuk menggunakan fungsi di atas.
Panduan composable ItemEntryBody()
- Dalam file
ui/item/ItemEntryScreen.kt, composableItemEntryBody()diterapkan sebagian untuk Anda sebagai bagian dari kode awal. Lihat composableItemEntryBody()dalam panggilan fungsiItemEntryScreen().
// No need to copy over, part of the starter code
ItemEntryBody(
itemUiState = viewModel.itemUiState,
onItemValueChange = viewModel::updateUiState,
onSaveClick = { },
modifier = Modifier
.padding(innerPadding)
.verticalScroll(rememberScrollState())
.fillMaxWidth()
)
- Perhatikan bahwa status UI dan lambda
updateUiStatediteruskan sebagai parameter fungsi. Lihat definisi fungsi untuk mengetahui cara status UI diupdate.
// No need to copy over, part of the starter code
@Composable
fun ItemEntryBody(
itemUiState: ItemUiState,
onItemValueChange: (ItemUiState) -> Unit,
onSaveClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
// ...
) {
ItemInputForm(
itemDetails = itemUiState.itemDetails,
onValueChange = onItemValueChange,
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = onSaveClick,
enabled = itemUiState.isEntryValid,
shape = MaterialTheme.shapes.small,
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(R.string.save_action))
}
}
}
Anda menampilkan ItemInputForm dan tombol Save dalam composable ini. Dalam composable ItemInputForm(), Anda menampilkan tiga kolom teks. Opsi Save hanya diaktifkan jika teks dimasukkan di kolom teks. Nilai isEntryValid bernilai benar jika teks di semua kolom teks valid (tidak kosong).
|
|
- Lihat implementasi fungsi composable
ItemInputForm()dan perhatikan parameter fungsionValueChange. Anda memperbarui nilaiitemDetailsdengan nilai yang dimasukkan oleh pengguna di kolom teks. Saat tombol Save diaktifkan,itemUiState.itemDetailsmemiliki nilai yang perlu disimpan.
// No need to copy over, part of the starter code
@Composable
fun ItemEntryBody(
//...
) {
Column(
// ...
) {
ItemInputForm(
itemDetails = itemUiState.itemDetails,
//...
)
//...
}
}
// No need to copy over, part of the starter code
@Composable
fun ItemInputForm(
itemDetails: ItemDetails,
modifier: Modifier = Modifier,
onValueChange: (ItemUiState) -> Unit = {},
enabled: Boolean = true
) {
Column(modifier = modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
OutlinedTextField(
value = itemUiState.name,
onValueChange = { onValueChange(itemDetails.copy(name = it)) },
//...
)
OutlinedTextField(
value = itemUiState.price,
onValueChange = { onValueChange(itemDetails.copy(price = it)) },
//...
)
OutlinedTextField(
value = itemUiState.quantity,
onValueChange = { onValueChange(itemDetails.copy(quantity = it)) },
//...
)
}
}
Menambahkan pemroses klik ke tombol Save
Untuk menggabungkan semuanya, tambahkan pengendali klik ke tombol Save. Dalam pengendali klik, Anda meluncurkan coroutine dan memanggil saveItem() untuk menyimpan data di database Room.
- Di
ItemEntryScreen.kt, dalam fungsi composableItemEntryScreen, buatvalbernamacoroutineScopedengan fungsi composablerememberCoroutineScope().
import androidx.compose.runtime.rememberCoroutineScope
val coroutineScope = rememberCoroutineScope()
- Update panggilan fungsi
ItemEntryBody()dan luncurkan coroutine dalam lambdaonSaveClick.
ItemEntryBody(
// ...
onSaveClick = {
coroutineScope.launch {
}
},
modifier = modifier.padding(innerPadding)
)
- Lihat implementasi fungsi
saveItem()di fileItemEntryViewModel.ktuntuk memeriksa apakahitemUiStatevalid, mengonversiitemUiStatemenjadi jenisItem, dan menyisipkannya dalam database menggunakanitemsRepository.insertItem().
// No need to copy over, you have already implemented this as part of the Room implementation
suspend fun saveItem() {
if (validateInput()) {
itemsRepository.insertItem(itemUiState.itemDetails.toItem())
}
}
- Di
ItemEntryScreen.kt, dalam fungsi composableItemEntryScreen, di dalam coroutine, panggilviewModel.saveItem()untuk menyimpan item dalam database.
ItemEntryBody(
// ...
onSaveClick = {
coroutineScope.launch {
viewModel.saveItem()
}
},
//...
)
Perhatikan bahwa Anda tidak menggunakan viewModelScope.launch() untuk saveItem() dalam file ItemEntryViewModel.kt, tetapi hal ini diperlukan untuk ItemEntryBody() saat Anda memanggil metode repositori. Anda hanya dapat memanggil fungsi penangguhan dari coroutine atau fungsi penangguhan lainnya. Fungsi viewModel.saveItem() merupakan fungsi penangguhan.
- Bangun dan jalankan aplikasi Anda.
- Ketuk + FAB.
- Di layar Add Item, tambahkan detail item dan ketuk Save. Perhatikan bahwa mengetuk tombol Save tidak akan menutup layar Add Item.

- Di lambda
onSaveClick, tambahkan panggilan kenavigateBack()setelah panggilan keviewModel.saveItem()untuk membuka kembali layar sebelumnya. FungsiItemEntryBody()terlihat seperti kode berikut:
ItemEntryBody(
itemUiState = viewModel.itemUiState,
onItemValueChange = viewModel::updateUiState,
onSaveClick = {
coroutineScope.launch {
viewModel.saveItem()
navigateBack()
}
},
modifier = modifier.padding(innerPadding)
)
- Jalankan kembali aplikasi dan lakukan langkah yang sama untuk memasukkan dan menyimpan data. Perhatikan bahwa kali ini aplikasi kembali ke layar Inventory.
Tindakan ini akan menyimpan data, tetapi Anda tidak dapat melihat data inventaris di aplikasi. Pada tugas berikutnya, Anda dapat menggunakan Database Inspector untuk melihat data yang disimpan.

10. Melihat isi database menggunakan Database Inspector
Database Inspector memungkinkan Anda memeriksa, membuat kueri, dan mengubah database aplikasi saat aplikasi sedang berjalan. Fitur ini sangat berguna untuk proses debug database. Database Inspector bekerja dengan SQLite biasa dan library yang dibuat pada SQLite, seperti Room. Database Inspector berfungsi paling baik pada emulator/perangkat yang menjalankan API level 26.
- Jalankan aplikasi Anda di emulator atau perangkat terhubung yang menjalankan API level 26 atau lebih tinggi, jika Anda belum melakukannya.
- Di Android Studio, pilih View > Tool Windows > App Inspection dari panel menu.
- Pilih tab Database Inspector.
- Di panel Database Inspector, pilih
com.example.inventorydari menu dropdown jika belum dipilih. item_database di aplikasi Inventory akan muncul di panel Databases.

- Luaskan node untuk item_database di panel Databases 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 mempertahankan data menggunakan Room. Pada codelab berikutnya, Anda akan menambahkan lazyColumn ke aplikasi untuk menampilkan item pada database, serta menambahkan fitur baru ke aplikasi, seperti kemampuan untuk menghapus dan memperbarui entity. Sampai jumpa!
11. Mendapatkan kode solusi
Kode solusi untuk codelab ini ada di repo GitHub. Untuk mendownload kode codelab yang sudah selesai, gunakan perintah git berikut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-inventory-app.git $ cd basic-android-kotlin-compose-training-inventory-app $ git checkout room
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi untuk codelab ini, lihat kode tersebut di GitHub.
12. Ringkasan
- Tentukan tabel Anda sebagai class data yang dianotasi dengan
@Entity. Tentukan properti yang dianotasi dengan@ColumnInfosebagai 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
@Querydengan string kueri SQLite sebagai parameter untuk kueri lainnya. - Gunakan Database Inspector untuk melihat data yang disimpan di database Android SQLite.
13. Pelajari 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



