Membaca dan memperbarui data dengan Room

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.

771c6a677ecd96c7.png

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

  1. Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
  2. Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.

5b0a76c50478a73f.png

  1. Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
  2. Temukan file di komputer Anda (mungkin di folder Downloads).
  3. Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.

Membuka project di Android Studio

  1. Mulai Android Studio.
  2. Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.

21f3eec988dcfbe9.png

  1. Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
  2. Klik dua kali pada folder project tersebut.
  3. Tunggu Android Studio membuka project.
  4. Klik tombol Run 11c34fc5e516fb1c.png untuk mem-build dan menjalankan aplikasi. Pastikan aplikasi berfungsi seperti yang diharapkan.
  5. 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.

d6e7b7b9f12e7a16.png

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.

  1. Di Item.kt, di bawah definisi class, tambahkan fungsi ekstensi bernama Item.getFormattedPrice() yang tidak menggunakan parameter dan menampilkan String. 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.

  1. Dalam paket com.example.inventory, tambahkan class Kotlin yang bernama ItemListAdapter. Teruskan fungsi yang disebut onItemClicked() sebagai parameter konstruktor yang menggunakan objek Item sebagai parameter.
  2. Ubah tanda tangan class ItemListAdapter untuk memperluas ListAdapter. Teruskan Item dan ItemListAdapter.ItemViewHolder sebagai parameter.
  3. Tambahkan parameter konstruktor DiffCallback; ListAdapter akan menggunakannya untuk mencari tahu apa yang berubah dalam daftar.
  4. Ganti metode yang diperlukan onCreateViewHolder() dan onBindViewHolder().
  5. Metode onCreateViewHolder() menampilkan ViewHolder baru saat RecyclerView memerlukannya.
  6. Di dalam metode onCreateViewHolder(), buat View baru, inflate dari file tata letak item_list_item.xml menggunakan class binding yang dihasilkan secara otomatis, ItemListItemBinding.
  7. Implementasikan metode onBindViewHolder(). Dapatkan item saat ini menggunakan metode getItem() dengan meneruskan posisinya.
  8. Tetapkan pemroses klik pada itemView, dan panggil fungsi onItemClicked() dalam pemroses.
  9. Tentukan class ItemViewHolder, perluas dari RecyclerView.ViewHolder.. Ganti fungsi bind(), teruskan objek Item.
  10. Tentukan objek pendamping. Di dalam objek pendamping, tentukan val dari jenis DiffUtil.ItemCallback<Item>() yang disebut DiffCallback. Ganti metode areItemsTheSame() dan areContentsTheSame() 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.

9c416f2fbf1e5ae2.png

  1. Di ItemListAdapter.kt, terapkan fungsi bind() di class ItemViewHolder. Ikat TextView itemName ke item.itemName. Dapatkan harga dalam format mata uang menggunakan fungsi ekstensi getFormattedPrice(), dan ikatkan ke TextView itemPrice. Konversikan nilai quantityInStock menjadi String, dan ikatkan ke TextView itemQuantity. 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.

  1. Di awal class InventoryViewModel, buat val yang bernama allItems dari jenis LiveData<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.

  1. Panggil getItems() di itemDao dan tetapkan ke allItems. Fungsi getItems() akan menampilkan Flow. Untuk menggunakan data sebagai nilai LiveData, gunakan fungsi asLiveData(). 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.

  1. Di ItemListFragment, di awal class, deklarasikan properti tetap private yang disebut viewModel dari jenis InventoryViewModel. Gunakan delegasi by untuk memberikan inisialisasi properti ke class activityViewModels. Teruskan konstruktor InventoryViewModelFactory.
private val viewModel: InventoryViewModel by activityViewModels {
   InventoryViewModelFactory(
       (activity?.application as InventoryApplication).database.itemDao()
   )
}

Impor androidx.fragment.app.activityViewModels saat diminta oleh Android Studio.

  1. Masih di dalam ItemListFragment, scroll ke fungsi onViewCreated(). Di bawah panggilan untuk super.onViewCreated(), nyatakan val yang bernama adapter. Inisialisasi properti adapter baru menggunakan konstruktor default, ItemListAdapter{} yang tidak meneruskan apa pun.
  2. Ikat adapter yang baru dibuat dengan recyclerView sebagai berikut:
val adapter = ItemListAdapter {
}
binding.recyclerView.adapter = adapter
  1. Masih di dalam onViewCreated(), setelah menyetel adaptor. Tambahkan observer di allItems untuk memproses perubahan data.
  2. Di dalam observer, panggil submitList() pada adapter 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)
   }
}
  1. 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)
   }
}

9c416f2fbf1e5ae2.png

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.

d699618f5d9437df.png

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

  1. Di ItemListFragment, scroll ke fungsi onViewCreated() untuk memperbarui definisi adaptor.
  2. Tambahkan lambda sebagai parameter konstruktor ke ItemListAdapter{}.
val adapter = ItemListAdapter {
}
  1. Di dalam lambda, buat val yang disebut action. Anda akan segera memperbaiki error inisialisasi lainnya.
val adapter = ItemListAdapter {
    val action
}
  1. Panggil metode actionItemListFragmentToItemDetailFragment() di ItemListFragmentDirections yang meneruskan id item. Tetapkan objek NavDirections yang ditampilkan ke action.
val adapter = ItemListAdapter {
   val action =    ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
}
  1. Di bawah definisi action, ambil instance NavController menggunakan this.findNavController() dan panggil navigate() di dalamnya dengan memasukkan action. Definisi adaptor akan terlihat seperti ini:
val adapter = ItemListAdapter {
   val action =   ItemListFragmentDirections.actionItemListFragmentToItemDetailFragment(it.id)
   this.findNavController().navigate(action)
}
  1. Jalankan aplikasi. Klik item di RecyclerView. Aplikasi akan membuka layar Item Details. Perhatikan bahwa detailnya kosong. Ketuk tombol, dan tidak ada yang terjadi.

196553111ee69beb.png

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.

  1. Dalam InventoryViewModel, tambahkan fungsi bernama retrieveItem() yang menggunakan Int untuk ID item dan menampilkan LiveData<Item>. Anda akan segera memperbaiki error ekspresi return.
fun retrieveItem(id: Int): LiveData<Item> {
}
  1. Di dalam fungsi baru, panggil getItem() pada itemDao, yang meneruskan parameter id. Fungsi getItem() akan menampilkan Flow. Untuk menggunakan nilai Flow sebagai LiveData, panggil fungsi asLiveData() dan gunakan ini sebagai hasil fungsi retrieveItem(). 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.

  1. Di ItemDetailFragment, tambahkan properti yang dapat diubah yang disebut item dari jenis entity Item. Anda akan menggunakan properti ini untuk menyimpan informasi tentang satu entity. Properti ini akan diinisialisasi nanti. Jadi, awali dengan lateinit.
lateinit var item: Item

Impor com.example.inventory.data.Item saat diminta oleh Android Studio.

  1. Di awal class ItemDetailFragment, deklarasikan properti tetap private yang disebut viewModel dari jenis InventoryViewModel. Gunakan delegasi by untuk memberikan inisialisasi properti ke class activityViewModels. Teruskan konstruktor InventoryViewModelFactory.
private val viewModel: InventoryViewModel by activityViewModels {
   InventoryViewModelFactory(
       (activity?.application as InventoryApplication).database.itemDao()
   )
}

Impor androidx.fragment.app.activityViewModels saat diminta oleh Android Studio.

  1. Masih di ItemDetailFragment, buat fungsi private bernama bind() yang menggunakan instance entity Item sebagai parameter dan tidak menampilkan apa pun.
private fun bind(item: Item) {
}
  1. Terapkan fungsi bind(), fungsi ini mirip dengan fungsi yang telah Anda lakukan di ItemListAdapter. Setel properti text dari TextView itemName ke item.itemName. Panggil getFormattedPrice() di properti item untuk memformat nilai harga, dan setel ke properti text dari TextView itemPrice. Konversikan quantityInStock ke String dan tetapkan ke properti text dari TextView itemQuantity.
private fun bind(item: Item) {
   binding.itemName.text = item.itemName
   binding.itemPrice.text = item.getFormattedPrice()
   binding.itemCount.text = item.quantityInStock.toString()
}
  1. Perbarui fungsi bind() untuk menggunakan fungsi cakupan apply{} 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()
   }
}
  1. Masih dalam ItemDetailFragment, ganti onViewCreated().
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
   super.onViewCreated(view, savedInstanceState)
}
  1. Di salah satu langkah sebelumnya, Anda telah meneruskan ID item sebagai argumen navigasi dari ItemListFragment ke ItemDetailFragment. Dalam onViewCreated(), di bawah panggilan ke fungsi super class, buat variabel tetap yang disebut id. Ambil dan tetapkan argumen navigasi ke variabel baru ini.
val id = navigationArgs.itemId
  1. Sekarang Anda akan menggunakan variabel id ini untuk mengambil detail item. Masih di dalam onViewCreated(), panggil fungsi retrieveItem() pada viewModel yang meneruskan id. Lampirkan observer ke nilai yang ditampilkan dengan meneruskan viewLifecycleOwner dan lambda.
viewModel.retrieveItem(id).observe(this.viewLifecycleOwner) {
   }
  1. Di dalam lambda, masukkan selectedItem sebagai parameter yang berisi entity Item yang diambil dari database. Pada isi fungsi lambda, tetapkan nilai selectedItem ke item. Panggil fungsi bind() dengan meneruskan item. 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)
   }
}
  1. 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.

  1. 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:

  1. Di InventoryViewModel, tambahkan fungsi pribadi bernama updateItem() yang mengambil instance class entity, Item dan tidak menampilkan apa-apa.
private fun updateItem(item: Item) {
}
  1. Terapkan metode baru, updateItem(). Untuk memanggil metode penangguhan update() dari class ItemDao, luncurkan coroutine menggunakan viewModelScope. Di dalam blok peluncuran, lakukan panggilan ke fungsi update() saat itemDao meneruskan item. Metode lengkap Anda akan terlihat seperti berikut.
private fun updateItem(item: Item) {
   viewModelScope.launch {
       itemDao.update(item)
   }
}
  1. Masih di dalam InventoryViewModel, tambahkan metode lain bernama sellItem() yang mengambil instance dari class entity Item dan tidak menampilkan apa pun.
fun sellItem(item: Item) {
}
  1. Di dalam fungsi sellItem(), tambahkan kondisi if untuk memastikan apakah item.quantityInStock lebih besar dari 0 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)
  1. Kembali ke fungsi sellItem() di InventoryViewModel. Di dalam blok if, buat properti yang tidak dapat diubah yang disebut newItem. Panggil fungsi copy() pada instance item yang meneruskan quantityInStock yang telah diperbarui sehingga akan mengurangi stok sebesar 1.
val newItem = item.copy(quantityInStock = item.quantityInStock - 1)
  1. Di bawah definisi newItem, lakukan panggilan ke fungsi updateItem() yang meneruskan entity baru yang diperbarui, yaitu newItem. 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)
   }
}
  1. Untuk menambahkan fitur stok penjualan, buka ItemDetailFragment. Scroll ke akhir fungsi bind(). Di dalam blok apply, tetapkan pemroses klik ke tombol Sell dan panggil fungsi sellItem() pada viewModel.
private fun bind(item: Item) {
binding.apply {

...
    sellItem.setOnClickListener { viewModel.sellItem(item) }
    }
}
  1. 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.

aa63ca761dc8f009.png

  1. 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.

3e099d3c55596938.png

  1. 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 dari 0 atau tidak. Berikan nama isStockAvailable() pada fungsi tersebut, yang menggunakan instance Item dan menampilkan Boolean.
fun isStockAvailable(item: Item): Boolean {
   return (item.quantityInStock > 0)
}
  1. Buka ItemDetailFragment, scroll ke fungsi bind(). Di dalam blok penerapan, panggil fungsi isStockAvailable() pada viewModel yang meneruskan item. Tetapkan nilai return ke properti isEnabled 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) }
   }
}
  1. Jalankan aplikasi Anda, perhatikan bahwa tombol Sell dinonaktifkan saat jumlah yang tersedia nol. Selamat Anda telah berhasil menerapkan fitur item penjualan ke aplikasi.

5e49db8451e77c2b.png

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:

  1. Di InventoryViewModel, tambahkan fungsi baru bernama deleteItem(), yang menggunakan instance class entity Item bernama item dan tidak menampilkan apa pun. Di dalam fungsi deleteItem(), luncurkan coroutine dengan viewModelScope. Di dalam blok launch, panggil metode delete() di itemDao yang meneruskan item.
fun deleteItem(item: Item) {
   viewModelScope.launch {
       itemDao.delete(item)
   }
}
  1. Di ItemDetailFragment, scroll ke awal fungsi deleteItem(). Panggil deleteItem() pada viewModel, teruskan item. Instance item 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()
}
  1. Masih dalam ItemDetailFragment, scroll ke fungsi showConfirmationDialog(). Fungsi ini diberikan untuk Anda sebagai bagian dari kode awal. Metode ini menampilkan dialog pemberitahuan untuk mendapatkan konfirmasi pengguna sebelum menghapus item dan memanggil fungsi deleteItem() 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:

728bfcbb997c8017.png

  1. Di ItemDetailFragment, di akhir fungsi bind(), di dalam blok apply, tetapkan pemroses klik ke tombol hapus. Panggil showConfirmationDialog() di dalam lambda pemroses klik.
private fun bind(item: Item) {
   binding.apply {
       ...
       deleteItem.setOnClickListener { showConfirmationDialog() }
   }
}
  1. 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.

c05318ab8c216fa1.png

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

  1. Di ItemDetailFragment, tambahkan fungsi private baru bernama editItem() yang tidak menggunakan parameter dan tidak menampilkan apa pun. Di langkah berikutnya, Anda akan menggunakan kembali fragment_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.

bcd407af7c515a21.png

  1. Dalam fungsi editItem(), buat variabel tetap yang disebut action. Lakukan panggilan ke actionItemDetailFragmentToAddItemFragment() pada ItemDetailFragmentDirections dengan meneruskan string judul, edit_fragment_title dan item id. Tetapkan nilai yang ditampilkan ke action. Di bawah definisi action, panggil this.findNavController().navigate() yang meneruskan action untuk membuka layar Edit Item.
private fun editItem() {
   val action = ItemDetailFragmentDirections.actionItemDetailFragmentToAddItemFragment(
       getString(R.string.edit_fragment_title),
       item.id
   )
   this.findNavController().navigate(action)
}
  1. Masih dalam ItemDetailFragment, scroll ke fungsi bind(). Di dalam blok apply, setel pemroses klik ke FAB, panggil fungsi editItem() dari lambda untuk membuka layar Edit Item.
private fun bind(item: Item) {
   binding.apply {
       ...
       editItem.setOnClickListener { editItem() }
   }
}
  1. 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.

a6a6583171b68230.png

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.

  1. Di AddItemFragment, tambahkan fungsi private baru untuk mengikat kolom teks dengan detail entity. Beri nama fungsi bind(), yang menggunakan instance class entity Item dan tidak menampilkan apa pun.
private fun bind(item: Item) {
}
  1. Implementasi fungsi bind() sangat mirip dengan yang sudah Anda lakukan sebelumnya di ItemDetailFragment. Di dalam fungsi bind(), bulatkan harga ke dua tempat desimal menggunakan fungsi format() dan tetapkan ke val bernama price, seperti yang ditunjukkan di bawah ini.
val price = "%.2f".format(item.itemPrice)
  1. Di bawah definisi price, gunakan fungsi cakupan apply pada properti binding seperti yang ditunjukkan di bawah.
binding.apply {
}
  1. Di dalam blok kode fungsi cakupan apply, tetapkan item.itemName ke properti teks itemName. Gunakan fungsi setText() dan teruskan string item.itemName dan TextView.BufferType.SPANNABLE sebagai BufferType.
binding.apply {
   itemName.setText(item.itemName, TextView.BufferType.SPANNABLE)
}

Impor android.widget.TextView saat diminta oleh Android Studio.

  1. Seperti langkah di atas, tetapkan properti teks dengan harga EditText seperti yang ditunjukkan di bawah ini. Agar dapat menyetel properti text dari kuantitas EditText, Anda harus mengonversi item.quantityInStock ke String. 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)
   }
}
  1. Masih di dalam AddItemFragment, scroll ke fungsi onViewCreated(). Setelah panggilan ke fungsi super class. Buat val dengan nama id dan ambil itemId dari argumen navigasi.
val id = navigationArgs.itemId
  1. Tambahkan blok if-else dengan kondisi untuk memastikan apakah id lebih besar dari nol atau tidak, dan pindahkan pemroses klik tombol Save ke blok else. Di dalam blok if, ambil entity menggunakan id dan tambahkan observer di dalamnya. Di dalam observer, perbarui properti item dan panggil bind() yang meneruskan item. 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()
       }
   }
}
  1. 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.

829ceb9dd7993215.png

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.

  1. Di InventoryViewModel, tambahkan fungsi private bernama getUpdatedItemEntry() yang menggunakan Int, dan tiga string untuk detail entity bernama itemName, itemPrice, dan itemCount. Tampilkan instance Item dari fungsi. Kode diberikan sebagai referensi Anda.
private fun getUpdatedItemEntry(
   itemId: Int,
   itemName: String,
   itemPrice: String,
   itemCount: String
): Item {
}
  1. Di dalam fungsi getUpdatedItemEntry(), buat instance Item menggunakan parameter fungsi, seperti yang ditunjukkan di bawah ini. Tampilkan instance Item 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()
   )
}
  1. Masih di dalam InventoryViewModel, tambahkan fungsi lain bernama updateItem(). Fungsi ini juga menggunakan Int 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
) {
}
  1. Dalam fungsi updateItem(), lakukan panggilan ke fungsi getUpdatedItemEntry() yang meneruskan informasi entity, yang diteruskan sebagai parameter fungsi, seperti yang ditunjukkan di bawah. Tetapkan nilai yang ditampilkan ke variabel tetap yang disebut updatedItem.
val updatedItem = getUpdatedItemEntry(itemId, itemName, itemPrice, itemCount)
  1. Tepat di bawah panggilan ke fungsi getUpdatedItemEntry(), lakukan panggilan ke fungsi updateItem() yang meneruskan updatedItem. 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)
}
  1. Kembali ke AddItemFragment, tambahkan fungsi pribadi bernama updateItem() tanpa parameter dan tidak menampilkan apa pun. Di dalam fungsi, tambahkan kondisi if untuk memvalidasi input pengguna dengan memanggil fungsi isEntryValid().
private fun updateItem() {
   if (isEntryValid()) {
   }
}
  1. Di dalam blok if, lakukan panggilan ke viewModel.updateItem() dengan meneruskan detail entity. Gunakan itemId 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()
)
  1. Di bawah panggilan fungsi updateItem(), tentukan val yang disebut action. Panggil actionAddItemFragmentToItemListFragment() pada AddItemFragmentDirections dan tetapkan nilai yang ditampilkan ke action. Buka ItemListFragment, panggil findNavController().navigate() yang meneruskan action.
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)
   }
}
  1. Masih dalam AddItemFragment, scroll ke fungsi bind(). Di dalam blok fungsi cakupan binding.apply, tetapkan pemroses klik untuk tombol Save. Lakukan panggilan ke fungsi updateItem() di dalam lambda seperti yang ditunjukkan di bawah.
private fun bind(item: Item) {
   ...
   binding.apply {
       ...
       saveAction.setOnClickListener { updateItem() }
   }
}
  1. Jalankan aplikasi. Coba edit item inventaris; Anda dapat mengedit item apa pun di database aplikasi Inventory.

1bbd094a77c25fc4.png

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

  1. Klik URL yang diberikan. Tindakan ini akan membuka halaman GitHub project di browser.
  2. Di halaman GitHub project, klik tombol Code yang akan menampilkan dialog.

5b0a76c50478a73f.png

  1. Di dialog, klik tombol Download ZIP untuk menyimpan project di komputer. Tunggu download selesai.
  2. Temukan file di komputer Anda (mungkin di folder Downloads).
  3. Klik dua kali pada file ZIP untuk mengekstraknya. Tindakan ini akan membuat folder baru yang berisi file project.

Membuka project di Android Studio

  1. Mulai Android Studio.
  2. Di jendela Welcome to Android Studio, klik Open an existing Android Studio project.

36cc44fcf0f89a1d.png

Catatan: Jika Android Studio sudah terbuka, pilih opsi menu File > New > Import Project.

21f3eec988dcfbe9.png

  1. Di dialog Import Project, buka lokasi folder project yang telah diekstrak (kemungkinan ada di folder Downloads).
  2. Klik dua kali pada folder project tersebut.
  3. Tunggu Android Studio membuka project.
  4. Klik tombol Run 11c34fc5e516fb1c.png untuk mem-build dan menjalankan aplikasi. Pastikan aplikasi berfungsi seperti yang diharapkan.
  5. 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 nilai LiveData, gunakan fungsi asLiveData().
  • 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

Referensi API

Referensi Kotlin