1. Sebelum memulai
Anda telah mempelajari codelab sebelumnya tentang cara menggunakan library persistensi Room, yaitu lapisan abstraksi di atas database SQLite untuk menyimpan data aplikasi. Dalam codelab ini, Anda akan menambahkan lebih banyak fitur ke aplikasi Inventory dan mempelajari cara membaca, menampilkan, memperbarui, dan menghapus data dari database SQLite menggunakan Room. Anda akan menggunakan RecyclerView
untuk menampilkan data dari database dan memperbarui data secara otomatis saat data pokok dalam database diubah.
Prasyarat
- Mengetahui cara membuat dan berinteraksi dengan database SQLite menggunakan library Room.
- Mengetahui cara membuat entity, DAO, dan class database.
- Mengetahui cara menggunakan objek akses data (DAO) untuk memetakan fungsi Kotlin ke kueri SQL.
- Mengetahui cara menampilkan item daftar dalam
RecyclerView
. - Telah mempelajari codelab sebelumnya dalam unit ini, Mempertahankan data dengan Room
Yang akan Anda pelajari
- Cara membaca dan menampilkan entity dari database SQLite.
- Cara memperbarui dan menghapus entity dari database SQLite menggunakan library Room.
Yang akan Anda build
- Anda akan mem-build aplikasi Inventory yang menampilkan daftar item inventaris. Aplikasi dapat memperbarui, mengedit, dan menghapus item dari database aplikasi menggunakan Room.
2. Ringkasan aplikasi awal
Codelab ini menggunakan kode solusi aplikasi Inventory dari codelab sebelumnya sebagai kode awal. Aplikasi awal sudah menyimpan data menggunakan library persistensi Room. Pengguna dapat menambahkan data ke database aplikasi menggunakan layar Add Item.
Catatan: Versi aplikasi awal saat ini tidak menampilkan tanggal yang tersimpan dalam database.
Dalam codelab ini, Anda akan memperluas aplikasi untuk membaca dan menampilkan data, memperbarui, dan menghapus entity di database menggunakan library Room.
Mendownload kode awal untuk codelab ini
Kode awal ini sama dengan kode solusi dari codelab sebelumnya.
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 berfungsi seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi diterapkan.
3. Menambahkan RecyclerView
Dalam tugas ini, Anda akan menambahkan RecyclerView
ke aplikasi untuk menampilkan data yang tersimpan dalam database.
Menambahkan fungsi bantuan untuk memformat harga
Di bawah ini adalah screenshot dari aplikasi akhir.
Perhatikan bahwa harga ditampilkan dalam format mata uang. Anda akan menambahkan fungsi ekstensi ke class Item
untuk mengonversi nilai ganda ke format mata uang yang diinginkan.
Fungsi Ekstensi
Kotlin menyediakan kemampuan untuk memperluas class dengan fungsi baru tanpa harus mewarisi dari class tersebut atau memodifikasi definisi class yang sudah ada. Ini artinya Anda dapat menambahkan fungsi ke class yang sudah ada tanpa harus mengakses kode sumbernya. Hal ini dilakukan melalui deklarasi khusus yang disebut ekstensi.
Misalnya, Anda dapat menulis fungsi baru untuk class dari library pihak ketiga yang tidak dapat Anda ubah. Fungsi tersebut tersedia untuk melakukan pemanggilan dengan cara biasa, seolah-olah merupakan metode class asli. Fungsi ini disebut fungsi ekstensi. (Ada juga properti ekstensi yang memungkinkan Anda menentukan properti baru untuk class yang sudah ada, tetapi ini berada di luar cakupan codelab ini.)
Fungsi ekstensi tidak benar-benar memodifikasi class, tetapi memungkinkan Anda menggunakan notasi titik saat memanggil fungsi pada objek class tersebut.
Misalnya, dalam cuplikan kode berikut, Anda memiliki class yang disebut Square
. Class ini memiliki properti untuk sisi dan fungsi untuk menghitung luas kotak. Perhatikan fungsi ekstensi Square.perimeter()
, nama fungsi diawali dengan class yang mengoperasikannya. Di dalam fungsi, Anda dapat mereferensikan properti publik dari class Square
.
Amati penggunaan fungsi ekstensi di fungsi main()
. Fungsi ekstensi yang dibuat, perimeter()
, dipanggil sebagai fungsi reguler di dalam class Square
tersebut.
Contoh:
class Square(val side: Double){
fun area(): Double{
return side * side;
}
}
// Extension function to calculate the perimeter of the square
fun Square.perimeter(): Double{
return 4 * side;
}
// Usage
fun main(args: Array<String>){
val square = Square(5.5);
val perimeterValue = square.perimeter()
println("Perimeter: $perimeterValue")
val areaValue = square.area()
println("Area: $areaValue")
}
Pada langkah ini, Anda akan memformat harga item menjadi string format mata uang. Secara umum, Anda tidak ingin mengubah class entity yang mewakili data hanya untuk memformat data (lihat prinsip tanggung jawab tunggal). Jadi, Anda hanya akan menambahkan fungsi ekstensi.
- Di
Item.kt
, di bawah definisi class, tambahkan fungsi ekstensi bernamaItem.getFormattedPrice()
yang tidak menggunakan parameter dan menampilkanString
. Perhatikan nama class dan notasi titik di nama fungsi.
fun Item.getFormattedPrice(): String =
NumberFormat.getCurrencyInstance().format(itemPrice)
Impor java.text.NumberFormat
saat diminta oleh Android Studio.
Menambahkan ListAdapter
Di langkah ini, Anda akan menambahkan adaptor daftar ke RecyclerView
. Karena Anda sudah terbiasa menerapkan adaptor dari codelab sebelumnya, maka petunjuknya akan dirangkum di bawah. File ItemListAdapter
yang telah selesai ada di akhir langkah ini untuk memudahkan dan membantu meningkatkan pemahaman Anda tentang konsep Room di codelab.
- Dalam paket
com.example.inventory
, tambahkan class Kotlin yang bernamaItemListAdapter
. Teruskan fungsi yang disebutonItemClicked()
sebagai parameter konstruktor yang menggunakan objekItem
sebagai parameter. - Ubah tanda tangan class
ItemListAdapter
untuk memperluasListAdapter
. TeruskanItem
danItemListAdapter.ItemViewHolder
sebagai parameter. - Tambahkan parameter konstruktor
DiffCallback
;ListAdapter
akan menggunakannya untuk mencari tahu apa yang berubah dalam daftar. - Ganti metode yang diperlukan
onCreateViewHolder()
danonBindViewHolder()
. - Metode
onCreateViewHolder()
menampilkanViewHolder
baru saat RecyclerView memerlukannya. - Di dalam metode
onCreateViewHolder()
, buatView
baru, inflate dari file tata letakitem_list_item.xml
menggunakan class binding yang dihasilkan secara otomatis,ItemListItemBinding
. - Implementasikan metode
onBindViewHolder()
. Dapatkan item saat ini menggunakan metodegetItem()
dengan meneruskan posisinya. - Tetapkan pemroses klik pada
itemView
, dan panggil fungsionItemClicked()
dalam pemroses. - Tentukan class
ItemViewHolder
, perluas dariRecyclerView.ViewHolder.
. Ganti fungsibind()
, teruskan objekItem
. - Tentukan objek pendamping. Di dalam objek pendamping, tentukan
val
dari jenisDiffUtil.ItemCallback<Item>()
yang disebutDiffCallback
. Ganti metodeareItemsTheSame()
danareContentsTheSame()
yang diperlukan, lalu tentukan metode tersebut.
Class yang sudah selesai akan terlihat seperti berikut.
package com.example.inventory
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.inventory.data.Item
import com.example.inventory.data.getFormattedPrice
import com.example.inventory.databinding.ItemListItemBinding
/**
* [ListAdapter] implementation for the recyclerview.
*/
class ItemListAdapter(private val onItemClicked: (Item) -> Unit) :
ListAdapter<Item, ItemListAdapter.ItemViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
return ItemViewHolder(
ItemListItemBinding.inflate(
LayoutInflater.from(
parent.context
)
)
)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener {
onItemClicked(current)
}
holder.bind(current)
}
class ItemViewHolder(private var binding: ItemListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
}
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return oldItem.itemName == newItem.itemName
}
}
}
}
Amati layar daftar inventaris dari aplikasi yang sudah selesai (aplikasi solusi dari akhir codelab ini). Perhatikan bahwa setiap elemen daftar menampilkan nama item inventaris, harga dalam format mata uang, dan stok yang tersedia saat ini. Pada langkah sebelumnya, Anda telah menggunakan file tata letak item_list_item.xml
dengan tiga TextView untuk membuat baris. Pada langkah berikutnya, Anda akan mengikat detail entity ke TextView ini.
- Di
ItemListAdapter.kt
, terapkan fungsibind()
di classItemViewHolder
. Ikat TextViewitemName
keitem.itemName
. Dapatkan harga dalam format mata uang menggunakan fungsi ekstensigetFormattedPrice()
, dan ikatkan ke TextViewitemPrice
. Konversikan nilaiquantityInStock
menjadiString
, dan ikatkan ke TextViewitemQuantity
. Metode yang sudah selesai akan terlihat seperti ini:
fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemQuantity.text = item.quantityInStock.toString()
}
}
Saat diminta oleh Android Studio, impor com.example.inventory.data.getFormattedPrice
.
Menggunakan ListAdapter
Dalam tugas ini, Anda akan memperbarui InventoryViewModel
dan ItemListFragment
untuk menampilkan detail item di layar menggunakan adaptor daftar yang telah dibuat di langkah sebelumnya.
- Di awal class
InventoryViewModel
, buatval
yang bernamaallItems
dari jenisLiveData<List<Item>>
untuk item dari database. Tidak perlu khawatir dengan error ini. Anda akan segera memperbaikinya.
val allItems: LiveData<List<Item>>
Impor androidx.lifecycle.LiveData
saat diminta oleh Android Studio.
- Panggil
getItems()
diitemDao
dan tetapkan keallItems
. FungsigetItems()
akan menampilkanFlow
. Untuk menggunakan data sebagai nilaiLiveData
, gunakan fungsiasLiveData()
. Definisi yang sudah selesai akan terlihat seperti ini:
val allItems: LiveData<List<Item>> = itemDao.getItems().asLiveData()
Impor androidx.lifecycle.asLiveData
saat diminta oleh Android Studio.
- Di
ItemListFragment
, di awal class, deklarasikan properti tetapprivate
yang disebutviewModel
dari jenisInventoryViewModel
. Gunakan delegasiby
untuk memberikan inisialisasi properti ke classactivityViewModels
. Teruskan konstruktorInventoryViewModelFactory
.
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
Impor androidx.fragment.app.activityViewModels
saat diminta oleh Android Studio.
- Masih di dalam
ItemListFragment
, scroll ke fungsionViewCreated()
. Di bawah panggilan untuksuper.onViewCreated()
, nyatakanval
yang bernamaadapter
. Inisialisasi propertiadapter
baru menggunakan konstruktor default,ItemListAdapter{}
yang tidak meneruskan apa pun. - Ikat
adapter
yang baru dibuat denganrecyclerView
sebagai berikut:
val adapter = ItemListAdapter {
}
binding.recyclerView.adapter = adapter
- Masih di dalam
onViewCreated()
, setelah menyetel adaptor. Tambahkan observer diallItems
untuk memproses perubahan data. - Di dalam observer, panggil
submitList()
padaadapter
dan teruskan daftar baru. Tindakan ini akan memperbarui RecyclerView dengan item baru di daftar.
viewModel.allItems.observe(this.viewLifecycleOwner) { items ->
items.let {
adapter.submitList(it)
}
}
- Pastikan metode
onViewCreated()
yang lengkap akan terlihat seperti di bawah ini. Jalankan aplikasi. Perhatikan bahwa daftar inventaris ditampilkan jika Anda menyimpan item di database aplikasi. Tambahkan beberapa item inventaris ke database aplikasi jika daftar tersebut kosong.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = ItemListAdapter {
}
binding.recyclerView.adapter = adapter
viewModel.allItems.observe(this.viewLifecycleOwner) { items ->
items.let {
adapter.submitList(it)
}
}
binding.recyclerView.layoutManager = LinearLayoutManager(this.context)
binding.floatingActionButton.setOnClickListener {
val action = ItemListFragmentDirections.actionItemListFragmentToAddItemFragment(
getString(R.string.add_fragment_title)
)
this.findNavController().navigate(action)
}
}
4. Menampilkan detail item
Dalam tugas ini, Anda akan membaca dan menampilkan detail entity di layar Detail Item. Anda akan menggunakan kunci utama (item id
) untuk membaca detail, seperti nama, harga, dan jumlah dari database aplikasi inventory, lalu menampilkannya di layar Item Details menggunakan file tata letak fragment_item_detail.xml
. File tata letak fragment_item_detail.xml
dirancang untuk Anda dan berisi tiga TextView yang menampilkan detail item.
Anda akan menerapkan langkah-langkah berikut dalam tugas ini:
- Tambahkan pengendali klik ke RecyclerView untuk membuka aplikasi ke layar Item Details.
- Dalam fragmen
ItemListFragment
, ambil data dari database dan tampilan. - Ikat TextView dengan data ViewModel.
Menambahkan pengendali klik
- Di
ItemListFragment
, scroll ke fungsionViewCreated()
untuk memperbarui definisi adaptor. - Tambahkan lambda sebagai parameter konstruktor ke
ItemListAdapter{}
.
val adapter = ItemListAdapter {
}
- Di dalam lambda, buat
val
yang disebutaction
. Anda akan segera memperbaiki error inisialisasi lainnya.
val adapter = ItemListAdapter {
val action
}
- Panggil metode
actionItemListFragmentToItemDetailFragment()
diItemListFragmentDirections
yang meneruskanid
item. Tetapkan objekNavDirections
yang ditampilkan keaction
.
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
}
- Di bawah definisi
action
, ambil instanceNavController
menggunakanthis.
findNavController
()
dan panggilnavigate()
di dalamnya dengan memasukkanaction
. Definisi adaptor akan terlihat seperti ini:
val adapter = ItemListAdapter {
val action = ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
this.findNavController().navigate(action)
}
- Jalankan aplikasi. Klik item di
RecyclerView
. Aplikasi akan membuka layar Item Details. Perhatikan bahwa detailnya kosong. Ketuk tombol, dan tidak ada yang terjadi.
Di langkah-langkah selanjutnya, Anda akan menampilkan detail entity di layar Item Details, serta menambahkan fungsi pada tombol jual (sell) dan hapus (delete).
Mengambil detail item
Di langkah ini, Anda akan menambahkan fungsi baru ke InventoryViewModel
untuk mengambil detail item dari database berdasarkan item id
. Di langkah berikutnya, Anda akan menggunakan fungsi ini untuk menampilkan detail entity di layar Item Details.
- Dalam
InventoryViewModel
, tambahkan fungsi bernamaretrieveItem()
yang menggunakanInt
untuk ID item dan menampilkanLiveData<Item>
. Anda akan segera memperbaiki error ekspresi return.
fun retrieveItem(id: Int): LiveData<Item> {
}
- Di dalam fungsi baru, panggil
getItem()
padaitemDao
, yang meneruskan parameterid
. FungsigetItem()
akan menampilkanFlow
. Untuk menggunakan nilaiFlow
sebagaiLiveData
, panggil fungsiasLiveData()
dan gunakan ini sebagai hasil fungsiretrieveItem()
. Fungsi yang sudah selesai akan terlihat seperti berikut:
fun retrieveItem(id: Int): LiveData<Item> {
return itemDao.getItem(id).asLiveData()
}
Mengikat data ke TextView
Di langkah ini, Anda akan membuat instance ViewModel di ItemDetailFragment
dan mengikat data ViewModel ke TextView dalam layar Item Details. Anda juga akan menyertakan observer ke data di ViewModel untuk terus memperbarui daftar inventaris di layar, jika data pokok dalam database berubah.
- Di
ItemDetailFragment
, tambahkan properti yang dapat diubah yang disebutitem
dari jenis entityItem
. Anda akan menggunakan properti ini untuk menyimpan informasi tentang satu entity. Properti ini akan diinisialisasi nanti. Jadi, awali denganlateinit
.
lateinit var item: Item
Impor com.example.inventory.data.Item
saat diminta oleh Android Studio.
- Di awal class
ItemDetailFragment
, deklarasikan properti tetapprivate
yang disebutviewModel
dari jenisInventoryViewModel
. Gunakan delegasiby
untuk memberikan inisialisasi properti ke classactivityViewModels
. Teruskan konstruktorInventoryViewModelFactory
.
private val viewModel: InventoryViewModel by activityViewModels {
InventoryViewModelFactory(
(activity?.application as InventoryApplication).database.itemDao()
)
}
Impor androidx.fragment.app.activityViewModels
saat diminta oleh Android Studio.
- Masih di
ItemDetailFragment
, buat fungsiprivate
bernamabind()
yang menggunakan instance entityItem
sebagai parameter dan tidak menampilkan apa pun.
private fun bind(item: Item) {
}
- Terapkan fungsi
bind()
, fungsi ini mirip dengan fungsi yang telah Anda lakukan diItemListAdapter
. Setel propertitext
dari TextViewitemName
keitem.itemName
. PanggilgetFormattedPrice
()
di propertiitem
untuk memformat nilai harga, dan setel ke propertitext
dari TextViewitemPrice
. KonversikanquantityInStock
keString
dan tetapkan ke propertitext
dari TextViewitemQuantity
.
private fun bind(item: Item) {
binding.itemName.text = item.itemName
binding.itemPrice.text = item.getFormattedPrice()
binding.itemCount.text = item.quantityInStock.toString()
}
- Perbarui fungsi
bind()
untuk menggunakan fungsi cakupanapply{}
ke blok kode seperti yang ditampilkan di bawah.
private fun bind(item: Item) {
binding.apply {
itemName.text = item.itemName
itemPrice.text = item.getFormattedPrice()
itemCount.text = item.quantityInStock.toString()
}
}
- Masih dalam
ItemDetailFragment
, gantionViewCreated()
.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
- Di salah satu langkah sebelumnya, Anda telah meneruskan ID item sebagai argumen navigasi dari
ItemListFragment
keItemDetailFragment
. DalamonViewCreated()
, di bawah panggilan ke fungsi super class, buat variabel tetap yang disebutid
. Ambil dan tetapkan argumen navigasi ke variabel baru ini.
val id = navigationArgs.itemId
- Sekarang Anda akan menggunakan variabel
id
ini untuk mengambil detail item. Masih di dalamonViewCreated()
, panggil fungsiretrieveItem()
padaviewModel
yang meneruskanid
. Lampirkan observer ke nilai yang ditampilkan dengan meneruskanviewLifecycleOwner
dan lambda.
viewModel.retrieveItem(id).observe(this.viewLifecycleOwner) {
}
- Di dalam lambda, masukkan
selectedItem
sebagai parameter yang berisi entityItem
yang diambil dari database. Pada isi fungsi lambda, tetapkan nilaiselectedItem
keitem
. Panggil fungsibind()
dengan meneruskanitem
. Fungsi yang sudah selesai akan terlihat seperti berikut.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val id = navigationArgs.itemId
viewModel.retrieveItem(id).observe(this.viewLifecycleOwner) { selectedItem ->
item = selectedItem
bind(item)
}
}
- Jalankan aplikasi. Klik elemen daftar di layar Inventory, layar Item Details akan ditampilkan. Perhatikan bahwa sekarang layar tidak kosong lagi karena menampilkan detail entity yang diambil dari database inventaris.
- Ketuk tombol Sell, Delete, dan FAB. Tidak ada yang terjadi. Pada tugas berikutnya, Anda akan menerapkan fungsi tombol ini.
5. Menerapkan item jual
Dalam tugas ini, Anda akan memperluas fitur aplikasi, mengimplementasikan fungsi penjualan. Berikut merupakan inti tingkat tinggi dari petunjuk untuk langkah ini.
- Menambahkan fungsi di ViewModel untuk memperbarui entity
- Membuat metode baru untuk mengurangi kuantitas dan memperbarui entity di database aplikasi.
- Lampirkan pemroses klik ke tombol Sell
- Nonaktifkan tombol Sell jika jumlahnya nol.
Mari membuat kode:
- Di
InventoryViewModel
, tambahkan fungsi pribadi bernamaupdateItem()
yang mengambil instance class entity,Item
dan tidak menampilkan apa-apa.
private fun updateItem(item: Item) {
}
- Terapkan metode baru,
updateItem()
. Untuk memanggil metode penangguhanupdate()
dari classItemDao
, luncurkan coroutine menggunakanviewModelScope
. Di dalam blok peluncuran, lakukan panggilan ke fungsiupdate()
saatitemDao
meneruskanitem
. Metode lengkap Anda akan terlihat seperti berikut.
private fun updateItem(item: Item) {
viewModelScope.launch {
itemDao.update(item)
}
}
- Masih di dalam
InventoryViewModel
, tambahkan metode lain bernamasellItem()
yang mengambil instance dari class entityItem
dan tidak menampilkan apa pun.
fun sellItem(item: Item) {
}
- Di dalam fungsi
sellItem()
, tambahkan kondisiif
untuk memastikan apakahitem.quantityInStock
lebih besar dari0
atau tidak.
fun sellItem(item: Item) {
if (item.quantityInStock > 0) {
}
}
Di dalam blok if
, Anda akan menggunakan fungsi copy()
untuk class Data untuk memperbarui entity.
Class data: copy()
Fungsi copy()
disediakan secara default untuk semua instance class data. Fungsi ini digunakan untuk menyalin objek untuk mengubah beberapa propertinya, tetapi tidak mengubah properti lainnya.
Misalnya, pertimbangkan class User
dan jack
instance-nya seperti yang ditampilkan di bawah ini. Jika Anda ingin membuat instance baru hanya dengan memperbarui properti age
, penerapannya adalah sebagai berikut:
Contoh
// Data class
data class User(val name: String = "", val age: Int = 0)
// Data class instance
val jack = User(name = "Jack", age = 1)
// A new instance is created with its age property changed, rest of the properties unchanged.
val olderJack = jack.copy(age = 2)
- Kembali ke fungsi
sellItem()
diInventoryViewModel
. Di dalam blokif
, buat properti yang tidak dapat diubah yang disebutnewItem
. Panggil fungsicopy()
pada instanceitem
yang meneruskanquantityInStock
yang telah diperbarui sehingga akan mengurangi stok sebesar1
.
val newItem = item.copy(quantityInStock = item.quantityInStock - 1)
- Di bawah definisi
newItem
, lakukan panggilan ke fungsiupdateItem()
yang meneruskan entity baru yang diperbarui, yaitunewItem
. Metode yang sudah selesai akan terlihat seperti berikut.
fun sellItem(item: Item) {
if (item.quantityInStock > 0) {
// Decrease the quantity by 1
val newItem = item.copy(quantityInStock = item.quantityInStock - 1)
updateItem(newItem)
}
}
- Untuk menambahkan fitur stok penjualan, buka
ItemDetailFragment
. Scroll ke akhir fungsibind()
. Di dalam blokapply
, tetapkan pemroses klik ke tombol Sell dan panggil fungsisellItem()
padaviewModel
.
private fun bind(item: Item) {
binding.apply {
...
sellItem.setOnClickListener { viewModel.sellItem(item) }
}
}
- Jalankan aplikasi. Pada layar Inventory, klik elemen daftar dengan kuantitas yang lebih besar dari nol. Layar Item Details akan ditampilkan. Ketuk tombol Sell, dan perhatikan bahwa nilai kuantitas dikurangi satu.
- Di layar Item Details, buat kuantitas 0 dengan mengetuk tombol Sell secara terus-menerus. (Tips: Pilih entity yang memiliki stok lebih sedikit atau buat entity baru dengan jumlah yang lebih sedikit). Setelah jumlahnya nol, ketuk tombol Sell. Tidak akan ada perubahan visual. Hal ini karena fungsi Anda
sellItem()
memastikan apakah jumlahnya lebih besar dari nol atau tidak sebelum memperbarui kuantitas.
- Untuk memberikan masukan yang lebih baik kepada pengguna, sebaiknya nonaktifkan tombol Sell saat tidak ada item yang dijual. Di
InventoryViewModel
, tambahkan fungsi untuk memastikan apakah jumlahnya lebih besar dari0
atau tidak. Berikan namaisStockAvailable()
pada fungsi tersebut, yang menggunakan instanceItem
dan menampilkanBoolean
.
fun isStockAvailable(item: Item): Boolean {
return (item.quantityInStock > 0)
}
- Buka
ItemDetailFragment
, scroll ke fungsibind()
. Di dalam blok penerapan, panggil fungsiisStockAvailable()
padaviewModel
yang meneruskanitem
. Tetapkan nilai return ke propertiisEnabled
tombol Sell. Kode Anda akan terlihat seperti ini.
private fun bind(item: Item) {
binding.apply {
...
sellItem.isEnabled = viewModel.isStockAvailable(item)
sellItem.setOnClickListener { viewModel.sellItem(item) }
}
}
- Jalankan aplikasi Anda, perhatikan bahwa tombol Sell dinonaktifkan saat jumlah yang tersedia nol. Selamat Anda telah berhasil menerapkan fitur item penjualan ke aplikasi.
Menghapus entity item
Serupa dengan tugas sebelumnya, Anda akan memperluas fitur aplikasi lebih jauh dengan mengimplementasikan fungsi hapus. Berikut adalah petunjuk tingkat tinggi untuk langkah ini dan ini jauh lebih mudah daripada menerapkan fitur penjualan.
- Tambahkan fungsi di ViewModel untuk menghapus entity dari database
- Tambahkan metode baru di
ItemDetailFragment
untuk memanggil fungsi hapus baru dan menangani navigasi. - Lampirkan pemroses klik ke tombol Delete.
Mari lanjut membuat kode:
- Di
InventoryViewModel
, tambahkan fungsi baru bernamadeleteItem()
, yang menggunakan instance class entityItem
bernamaitem
dan tidak menampilkan apa pun. Di dalam fungsideleteItem()
, luncurkan coroutine denganviewModelScope
. Di dalam bloklaunch
, panggil metodedelete()
diitemDao
yang meneruskanitem
.
fun deleteItem(item: Item) {
viewModelScope.launch {
itemDao.delete(item)
}
}
- Di
ItemDetailFragment
, scroll ke awal fungsideleteItem()
. PanggildeleteItem()
padaviewModel
, teruskanitem
. Instanceitem
berisi entity yang saat ini ditampilkan di layar Item Details. Metode Anda yang sudah selesai akan terlihat seperti berikut ini.
private fun deleteItem() {
viewModel.deleteItem(item)
findNavController().navigateUp()
}
- Masih dalam
ItemDetailFragment
, scroll ke fungsishowConfirmationDialog()
. Fungsi ini diberikan untuk Anda sebagai bagian dari kode awal. Metode ini menampilkan dialog pemberitahuan untuk mendapatkan konfirmasi pengguna sebelum menghapus item dan memanggil fungsideleteItem()
saat tombol positif diketuk.
private fun showConfirmationDialog() {
MaterialAlertDialogBuilder(requireContext())
...
.setPositiveButton(getString(R.string.yes)) { _, _ ->
deleteItem()
}
.show()
}
Fungsi showConfirmationDialog()
menampilkan dialog notifikasi yang terlihat seperti berikut:
- Di
ItemDetailFragment
, di akhir fungsibind()
, di dalam blokapply
, tetapkan pemroses klik ke tombol hapus. PanggilshowConfirmationDialog()
di dalam lambda pemroses klik.
private fun bind(item: Item) {
binding.apply {
...
deleteItem.setOnClickListener { showConfirmationDialog() }
}
}
- Jalankan aplikasi Anda. Pilih elemen daftar di layar daftar Inventory, di layar Item Details, ketuk tombol Delete. Ketuk Yes dan aplikasi akan membuka kembali layar Inventory. Perhatikan bahwa entity yang dihapus tidak ada lagi di database aplikasi. Selamat Anda telah berhasil menerapkan fitur hapus.
Mengedit entity item
Serupa dengan tugas sebelumnya, di tugas ini Anda akan menambahkan peningkatan fitur lainnya di aplikasi. Anda akan menerapkan entity item edit.
Berikut adalah langkah-langkah cepat untuk mengedit entity dalam database aplikasi:
- Gunakan kembali layar Add Item dengan mengubah judul fragmen ke Edit Item.
- Tambahkan pemroses ke FAB, untuk membuka layar Edit Item.
- Isi TextView dengan detail entity.
- Perbarui entity dalam database menggunakan Room.
Menambahkan pemroses klik ke FAB
- Di
ItemDetailFragment
, tambahkan fungsiprivate
baru bernamaeditItem()
yang tidak menggunakan parameter dan tidak menampilkan apa pun. Di langkah berikutnya, Anda akan menggunakan kembalifragment_add_item.xml
dengan memperbarui judul layar menjadi Edit Item. Untuk mencapai ini, Anda akan mengirimkan string judul fragmen bersama dengan ID item sebagai bagian dari tindakan.
private fun editItem() {
}
Setelah Anda mengubah judul fragmen, layar Edit Item akan terlihat seperti berikut.
- Dalam fungsi
editItem()
, buat variabel tetap yang disebutaction
. Lakukan panggilan keactionItemDetailFragmentToAddItemFragment()
padaItemDetailFragmentDirections
dengan meneruskan string judul,edit_fragment_title
dan itemid
. Tetapkan nilai yang ditampilkan keaction
. Di bawah definisiaction
, panggilthis.findNavController().navigate()
yang meneruskanaction
untuk membuka layar Edit Item.
private fun editItem() {
val action = ItemDetailFragmentDirections.actionItemDetailFragmentToAddItemFragment(
getString(R.string.edit_fragment_title),
item.id
)
this.findNavController().navigate(action)
}
- Masih dalam
ItemDetailFragment
, scroll ke fungsibind()
. Di dalam blokapply
, setel pemroses klik ke FAB, panggil fungsieditItem()
dari lambda untuk membuka layar Edit Item.
private fun bind(item: Item) {
binding.apply {
...
editItem.setOnClickListener { editItem() }
}
}
- Jalankan aplikasi. Buka layar Item Details. Klik FAB. Perhatikan bahwa judul layar diubah ke Edit Item, tetapi semua kolom teks kosong. Di langkah berikutnya, Anda akan memperbaikinya.
Mengisi TextView
Di langkah ini, Anda akan mengisi kolom teks di layar Edit Item dengan detail entity. Anda akan menambahkan fungsi baru ke file Kotlin AddItemFragment.kt
karena kami menggunakan layar Add Item
.
- Di
AddItemFragment
, tambahkan fungsiprivate
baru untuk mengikat kolom teks dengan detail entity. Beri nama fungsibind()
, yang menggunakan instance class entity Item dan tidak menampilkan apa pun.
private fun bind(item: Item) {
}
- Implementasi fungsi
bind()
sangat mirip dengan yang sudah Anda lakukan sebelumnya diItemDetailFragment
. Di dalam fungsibind()
, bulatkan harga ke dua tempat desimal menggunakan fungsiformat()
dan tetapkan keval
bernamaprice
, seperti yang ditunjukkan di bawah ini.
val price = "%.2f".format(item.itemPrice)
- Di bawah definisi
price
, gunakan fungsi cakupanapply
pada propertibinding
seperti yang ditunjukkan di bawah.
binding.apply {
}
- Di dalam blok kode fungsi cakupan
apply
, tetapkanitem.itemName
ke properti teksitemName
. Gunakan fungsisetText()
dan teruskan stringitem.itemName
danTextView.BufferType.SPANNABLE
sebagaiBufferType
.
binding.apply {
itemName.setText(item.itemName, TextView.BufferType.SPANNABLE)
}
Impor android.widget.TextView
saat diminta oleh Android Studio.
- Seperti langkah di atas, tetapkan properti teks dengan harga
EditText
seperti yang ditunjukkan di bawah ini. Agar dapat menyetel propertitext
dari kuantitas EditText, Anda harus mengonversiitem.quantityInStock
keString
. Fungsi Anda yang sudah selesai akan terlihat seperti ini.
private fun bind(item: Item) {
val price = "%.2f".format(item.itemPrice)
binding.apply {
itemName.setText(item.itemName, TextView.BufferType.SPANNABLE)
itemPrice.setText(price, TextView.BufferType.SPANNABLE)
itemCount.setText(item.quantityInStock.toString(), TextView.BufferType.SPANNABLE)
}
}
- Masih di dalam
AddItemFragment
, scroll ke fungsionViewCreated()
. Setelah panggilan ke fungsi super class. Buatval
dengan namaid
dan ambilitemId
dari argumen navigasi.
val id = navigationArgs.itemId
- Tambahkan blok
if-else
dengan kondisi untuk memastikan apakahid
lebih besar dari nol atau tidak, dan pindahkan pemroses klik tombol Save ke blokelse
. Di dalam blokif
, ambil entity menggunakanid
dan tambahkan observer di dalamnya. Di dalam observer, perbarui propertiitem
dan panggilbind()
yang meneruskanitem
. Fungsi lengkap disediakan agar Anda dapat menyalin dan menempel. Sangat sederhana dan mudah dipahami karena Anda tinggal mengartikannya sendiri.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val id = navigationArgs.itemId
if (id > 0) {
viewModel.retrieveItem(id).observe(this.viewLifecycleOwner) { selectedItem ->
item = selectedItem
bind(item)
}
} else {
binding.saveAction.setOnClickListener {
addNewItem()
}
}
}
- Jalankan aplikasi. Buka Item Details, ketuk + FAB. Perhatikan bahwa kolom diisi dengan detail item. Edit jumlah stok atau kolom lainnya, lalu ketuk tombol simpan (save). Tidak ada yang terjadi. Ini karena Anda tidak memperbarui entity dalam database aplikasi. Anda akan segera memperbaikinya.
Mengupdate entity menggunakan Room
Di tugas terakhir ini, tambahkan bagian akhir kode untuk mengimplementasikan fungsi update. Anda akan menentukan fungsi yang diperlukan di ViewModel dan menggunakannya di AddItemFragment
.
Ini waktunya membuat kode lagi.
- Di
InventoryViewModel
, tambahkan fungsiprivate
bernamagetUpdatedItemEntry()
yang menggunakanInt
, dan tiga string untuk detail entity bernamaitemName
,itemPrice
, danitemCount
. Tampilkan instanceItem
dari fungsi. Kode diberikan sebagai referensi Anda.
private fun getUpdatedItemEntry(
itemId: Int,
itemName: String,
itemPrice: String,
itemCount: String
): Item {
}
- Di dalam fungsi
getUpdatedItemEntry()
, buat instance Item menggunakan parameter fungsi, seperti yang ditunjukkan di bawah ini. Tampilkan instanceItem
dari fungsi.
private fun getUpdatedItemEntry(
itemId: Int,
itemName: String,
itemPrice: String,
itemCount: String
): Item {
return Item(
id = itemId,
itemName = itemName,
itemPrice = itemPrice.toDouble(),
quantityInStock = itemCount.toInt()
)
}
- Masih di dalam
InventoryViewModel
, tambahkan fungsi lain bernamaupdateItem()
. Fungsi ini juga menggunakanInt
dan tiga string detail entity dan tidak menampilkan apa pun. Gunakan nama variabel dari cuplikan kode berikut.
fun updateItem(
itemId: Int,
itemName: String,
itemPrice: String,
itemCount: String
) {
}
- Dalam fungsi
updateItem()
, lakukan panggilan ke fungsigetUpdatedItemEntry()
yang meneruskan informasi entity, yang diteruskan sebagai parameter fungsi, seperti yang ditunjukkan di bawah. Tetapkan nilai yang ditampilkan ke variabel tetap yang disebutupdatedItem
.
val updatedItem = getUpdatedItemEntry(itemId, itemName, itemPrice, itemCount)
- Tepat di bawah panggilan ke fungsi
getUpdatedItemEntry()
, lakukan panggilan ke fungsiupdateItem()
yang meneruskanupdatedItem
. Fungsi yang sudah selesai akan terlihat seperti ini:
fun updateItem(
itemId: Int,
itemName: String,
itemPrice: String,
itemCount: String
) {
val updatedItem = getUpdatedItemEntry(itemId, itemName, itemPrice, itemCount)
updateItem(updatedItem)
}
- Kembali ke
AddItemFragment
, tambahkan fungsi pribadi bernamaupdateItem()
tanpa parameter dan tidak menampilkan apa pun. Di dalam fungsi, tambahkan kondisiif
untuk memvalidasi input pengguna dengan memanggil fungsiisEntryValid()
.
private fun updateItem() {
if (isEntryValid()) {
}
}
- Di dalam blok
if
, lakukan panggilan keviewModel.updateItem()
dengan meneruskan detail entity. GunakanitemId
dari argumen navigasi, dan detail entity lainnya seperti nama, harga, dan kuantitas dari EditText seperti yang ditunjukkan di bawah.
viewModel.updateItem(
this.navigationArgs.itemId,
this.binding.itemName.text.toString(),
this.binding.itemPrice.text.toString(),
this.binding.itemCount.text.toString()
)
- Di bawah panggilan fungsi
updateItem()
, tentukanval
yang disebutaction
. PanggilactionAddItemFragmentToItemListFragment()
padaAddItemFragmentDirections
dan tetapkan nilai yang ditampilkan keaction
. BukaItemListFragment
, panggilfindNavController().navigate()
yang meneruskanaction
.
private fun updateItem() {
if (isEntryValid()) {
viewModel.updateItem(
this.navigationArgs.itemId,
this.binding.itemName.text.toString(),
this.binding.itemPrice.text.toString(),
this.binding.itemCount.text.toString()
)
val action = AddItemFragmentDirections.actionAddItemFragmentToItemListFragment()
findNavController().navigate(action)
}
}
- Masih dalam
AddItemFragment
, scroll ke fungsibind()
. Di dalam blok fungsi cakupanbinding.
apply
, tetapkan pemroses klik untuk tombol Save. Lakukan panggilan ke fungsiupdateItem()
di dalam lambda seperti yang ditunjukkan di bawah.
private fun bind(item: Item) {
...
binding.apply {
...
saveAction.setOnClickListener { updateItem() }
}
}
- Jalankan aplikasi. Coba edit item inventaris; Anda dapat mengedit item apa pun di database aplikasi Inventory.
Selamat atas pembuatan aplikasi pertama Anda untuk menggunakan Room guna mengelola database aplikasi.
6. 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 berfungsi seperti yang diharapkan.
- Cari file project di jendela alat Project untuk melihat cara aplikasi diterapkan.
7. Ringkasan
- Kotlin menyediakan kemampuan untuk memperluas class dengan fungsi baru tanpa harus mewarisi dari class tersebut atau memodifikasi definisi class yang sudah ada. Hal ini dilakukan melalui deklarasi khusus yang disebut ekstensi.
- Untuk menggunakan data
Flow
sebagai nilaiLiveData
, gunakan fungsiasLiveData()
. - Fungsi
copy()
disediakan secara default untuk semua instance class data. Fungsi ini memungkinkan Anda menyalin objek dan mengubah beberapa propertinya, sembari tetap membuat sisa propertinya tidak berubah.
8. Mempelajari lebih lanjut
Dokumentasi Developer Android
- Meneruskan data antartujuan
- String Android
- Pemformat Android
- Mendebug database dengan Database Inspector
- Menyimpan data di dalam database lokal menggunakan Room
Referensi API
Referensi Kotlin