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 LazyColumn untuk menampilkan data dari database dan memperbarui data secara otomatis saat data pokok dalam database berubah.

Prasyarat

  • Kemampuan membuat dan berinteraksi dengan database SQLite menggunakan library Room.
  • Kemampuan membuat entity, DAO, dan class database.
  • Kemampuan menggunakan objek akses data (DAO) untuk memetakan fungsi Kotlin ke kueri SQL.
  • Kemampuan untuk menampilkan item daftar di LazyColumn.
  • Penyelesaian codelab sebelumnya di 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

  • Aplikasi Inventory yang menampilkan daftar item inventaris dan dapat memperbarui, mengedit, serta menghapus item dari database aplikasi menggunakan Room.

Yang akan Anda butuhkan

  • Komputer dengan Android Studio

2. Ringkasan aplikasi awal

Codelab ini menggunakan kode solusi aplikasi Inventory dari codelab sebelumnya, Mempertahankan data dengan Room sebagai kode awal. Aplikasi awal sudah menyimpan data dengan library persistensi Room. Pengguna dapat menggunakan layar Add Item untuk menambahkan data ke database aplikasi.

Layar Tambahkan Item dengan detail item yang terisi.

Inventaris kosong layar ponsel

Dalam codelab ini, Anda akan memperluas aplikasi untuk membaca dan menampilkan data, serta memperbarui dan menghapus entity di database menggunakan library Room.

Mendownload kode awal untuk codelab ini

Untuk memulai, download kode awal:

$ 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 awal untuk codelab ini, lihat kode tersebut di GitHub.

3. Mengupdate status UI

Dalam tugas ini, Anda akan menambahkan LazyColumn ke aplikasi untuk menampilkan data yang tersimpan dalam database.

Layar ponsel dengan item inventaris

Panduan fungsi composable HomeScreen

  • Buka file ui/home/HomeScreen.kt dan lihat composable HomeScreen().
@Composable
fun HomeScreen(
    navigateToItemEntry: () -> Unit,
    navigateToItemUpdate: (Int) -> Unit,
    modifier: Modifier = Modifier,
) {
    val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()

    Scaffold(
        topBar = {
            // Top app with app title
        },
        floatingActionButton = {
            FloatingActionButton(
                // onClick details
            ) {
                Icon(
                    // Icon details
                )
            }
        },
    ) { innerPadding ->
     
       // Display List header and List of Items
        HomeBody(
            itemList = listOf(),  // Empty list is being passed in for itemList
            onItemClick = navigateToItemUpdate,
            modifier = modifier.padding(innerPadding)
                              .fillMaxSize()
        )
    }

Fungsi composable ini menampilkan item berikut:

  • Panel aplikasi atas dengan judul aplikasi
  • Tombol tindakan mengambang (FAB) untuk penambahan item baru ke inventaris 7b1535d90ee957fa.png
  • Fungsi composable HomeBody()

Fungsi composable HomeBody() menampilkan item inventaris berdasarkan daftar yang diteruskan. Sebagai bagian dari penerapan kode awal, daftar kosong (listOf()) diteruskan ke fungsi composable HomeBody(). Untuk meneruskan daftar inventaris ke composable ini, Anda harus mengambil data inventaris dari repositori dan meneruskannya ke HomeViewModel.

Memberikan status UI di HomeViewModel

Saat menambahkan metode ke ItemDao untuk mendapatkan item- getItem() dan getAllItems()- Anda menentukan Flow sebagai jenis nilai yang ditampilkan. Ingat kembali bahwa Flow mewakili aliran data generik. Dengan menampilkan Flow, Anda hanya perlu memanggil metode dari DAO secara eksplisit satu kali untuk siklus proses tertentu. Room menangani update pada data pokok secara asinkron.

Mendapatkan data dari flow disebut mengumpulkan dari flow. Saat mengumpulkan dari flow di lapisan UI, ada beberapa hal yang perlu dipertimbangkan.

  • Peristiwa siklus proses seperti perubahan konfigurasi, misalnya memutar perangkat, menyebabkan aktivitas dibuat ulang. Ini menyebabkan rekomposisi dan pengumpulan dari Flow Anda diulang kembali.
  • Anda ingin nilai di-cache sebagai status sehingga data yang ada tidak hilang di antara peristiwa siklus proses.
  • Flow harus dibatalkan jika tidak ada observer yang tersisa, seperti setelah siklus proses composable berakhir.

Cara yang direkomendasikan untuk mengekspos Flow dari ViewModel adalah dengan StateFlow. Penggunaan StateFlow memungkinkan data disimpan dan diamati, terlepas dari siklus proses UI. Untuk mengonversi Flow menjadi StateFlow, Anda menggunakan operator stateIn.

Operator stateIn memiliki tiga parameter yang dijelaskan di bawah:

  • scope - viewModelScope menentukan siklus proses StateFlow. Jika viewModelScope dibatalkan, StateFlow juga akan dibatalkan.
  • started - Pipeline hanya boleh aktif jika UI terlihat. SharingStarted.WhileSubscribed() digunakan untuk melakukannya. Untuk mengonfigurasi penundaan (dalam milidetik) antara hilangnya pelanggan terakhir dan penghentian coroutine berbagi, teruskan TIMEOUT_MILLIS ke metode SharingStarted.WhileSubscribed().
  • initialValue - Menetapkan nilai awal flow status ke HomeUiState().

Setelah mengonversi Flow menjadi StateFlow, Anda dapat mengumpulkannya menggunakan metode collectAsState(), yang mengonversi datanya menjadi State dari jenis yang sama.

Pada langkah ini, Anda akan mengambil semua item dalam database Room sebagai API StateFlow yang dapat diamati untuk status UI. Saat data Inventaris Room berubah, UI akan otomatis diperbarui.

  1. Buka file ui/home/HomeViewModel.kt, yang berisi konstanta TIMEOUT_MILLIS dan class data HomeUiState dengan daftar item sebagai parameter konstruktor.
// No need to copy over, this code is part of starter code

class HomeViewModel : ViewModel() {

    companion object {
        private const val TIMEOUT_MILLIS = 5_000L
    }
}

data class HomeUiState(val itemList: List<Item> = listOf())
  1. Di dalam class HomeViewModel, deklarasikan val yang disebut homeUiState dari jenis StateFlow<HomeUiState>. Anda akan segera mengatasi error inisialisasi.
val homeUiState: StateFlow<HomeUiState>
  1. Panggil getAllItemsStream() di itemsRepository dan tetapkan ke homeUiState yang baru saja Anda deklarasikan.
val homeUiState: StateFlow<HomeUiState> =
    itemsRepository.getAllItemsStream()

Anda sekarang mendapatkan error - Referensi yang belum terselesaikan: itemsRepository. Untuk mengatasi error Referensi yang belum terselesaikan, Anda harus meneruskan objek ItemsRepository ke HomeViewModel.

  1. Tambahkan parameter konstruktor jenis ItemsRepository ke class HomeViewModel.
import com.example.inventory.data.ItemsRepository

class HomeViewModel(itemsRepository: ItemsRepository): ViewModel() {
  1. Di file ui/AppViewModelProvider.kt, di penginisialisasi HomeViewModel, teruskan objek ItemsRepository seperti yang ditunjukkan.
initializer {
    HomeViewModel(inventoryApplication().container.itemsRepository)
}
  1. Kembali ke file HomeViewModel.kt. Perhatikan error ketidakcocokan jenis. Untuk mengatasi hal ini, tambahkan peta transformasi seperti yang ditunjukkan di bawah.
val homeUiState: StateFlow<HomeUiState> =
    itemsRepository.getAllItemsStream().map { HomeUiState(it) }

Android Studio masih menampilkan error ketidakcocokan jenis. Error ini terjadi karena homeUiState adalah jenis StateFlow dan getAllItemsStream() menampilkan Flow.

  1. Gunakan operator stateIn untuk mengonversi Flow menjadi StateFlow. StateFlow adalah API yang dapat diamati untuk status UI, yang memungkinkan UI mengupdate dirinya sendiri.
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

val homeUiState: StateFlow<HomeUiState> =
    itemsRepository.getAllItemsStream().map { HomeUiState(it) }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
            initialValue = HomeUiState()
        )
  1. Build aplikasi untuk memastikan tidak ada error dalam kode. Tidak akan ada perubahan visual.

4. Menampilkan data Inventaris

Dalam tugas ini, Anda mengumpulkan dan mengupdate status UI di HomeScreen.

  1. Di file HomeScreen.kt, pada fungsi composable HomeScreen, tambahkan parameter fungsi baru dari jenis HomeViewModel dan lakukan inisialisasi.
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.inventory.ui.AppViewModelProvider


@Composable
fun HomeScreen(
    navigateToItemEntry: () -> Unit,
    navigateToItemUpdate: (Int) -> Unit,
    modifier: Modifier = Modifier,
    viewModel: HomeViewModel = viewModel(factory = AppViewModelProvider.Factory)
)
  1. Dalam fungsi composable HomeScreen, tambahkan val yang disebut homeUiState untuk mengumpulkan status UI dari HomeViewModel. Anda menggunakan collectAsState(), yang mengumpulkan nilai dari StateFlow dan mewakili nilai terbarunya melalui State.
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue

val homeUiState by viewModel.homeUiState.collectAsState()
  1. Update panggilan fungsi HomeBody() dan teruskan homeUiState.itemList ke parameter itemList.
HomeBody(
    itemList = homeUiState.itemList,
    onItemClick = navigateToItemUpdate,
    modifier = modifier.padding(innerPadding)
)
  1. Jalankan aplikasi. Perhatikan bahwa daftar inventaris ditampilkan jika Anda menyimpan item di database aplikasi. Jika daftar kosong, tambahkan beberapa item inventaris ke database aplikasi.

Layar ponsel dengan item inventaris

5. Menguji database Anda

Codelab sebelumnya membahas pentingnya menguji kode Anda. Dalam tugas ini, Anda akan menambahkan beberapa pengujian unit untuk menguji kueri DAO, lalu menambahkan lebih banyak pengujian seiring progres Anda dalam codelab.

Metode yang direkomendasikan untuk menguji penerapan database adalah menulis pengujian JUnit yang berjalan di perangkat Android. Pengujian ini tidak memerlukan pembuatan aktivitas sehingga akan lebih cepat dijalankan daripada pengujian UI.

  1. Dalam file build.gradle.kts (Module :app), perhatikan dependensi berikut untuk Espresso dan JUnit.
// Testing
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
  1. Beralihlah ke tampilan Project, lalu klik kanan pada src > New > Directory untuk membuat set sumber pengujian bagi pengujian Anda.

9121189f4a0d2613.png

  1. Pilih androidTest/kotlin dari pop-up New Directory.

fba4ed57c7589f7f.png

  1. Buat class Kotlin bernama ItemDaoTest.kt.
  2. Anotasikan class ItemDaoTest dengan @RunWith(AndroidJUnit4::class). Class Anda sekarang terlihat seperti kode contoh berikut:
package com.example.inventory

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class ItemDaoTest {
}
  1. Dalam class, tambahkan variabel var pribadi dari jenis ItemDao dan InventoryDatabase.
import com.example.inventory.data.InventoryDatabase
import com.example.inventory.data.ItemDao

private lateinit var itemDao: ItemDao
private lateinit var inventoryDatabase: InventoryDatabase
  1. Tambahkan fungsi untuk membuat database dan menganotasinya dengan @Before agar dapat berjalan sebelum setiap pengujian.
  2. Dalam metode, lakukan inisialisasi itemDao.
import android.content.Context
import androidx.room.Room
import androidx.test.core.app.ApplicationProvider
import org.junit.Before

@Before
fun createDb() {
    val context: Context = ApplicationProvider.getApplicationContext()
    // Using an in-memory database because the information stored here disappears when the
    // process is killed.
    inventoryDatabase = Room.inMemoryDatabaseBuilder(context, InventoryDatabase::class.java)
        // Allowing main thread queries, just for testing.
        .allowMainThreadQueries()
        .build()
    itemDao = inventoryDatabase.itemDao()
}

Dalam fungsi ini, Anda menggunakan database dalam memori dan tidak mempertahankannya di disk. Untuk melakukannya, gunakan fungsi inMemoryDatabaseBuilder(). Anda melakukan ini karena informasi tidak perlu dipertahankan, tetapi harus dihapus saat proses dihentikan. Anda menjalankan kueri DAO di thread utama dengan .allowMainThreadQueries(), hanya untuk pengujian.

  1. Tambahkan fungsi lain untuk menutup database. Anotasikan dengan @After untuk menutup database dan menjalankannya setelah setiap pengujian.
import org.junit.After
import java.io.IOException

@After
@Throws(IOException::class)
fun closeDb() {
    inventoryDatabase.close()
}
  1. Deklarasikan item di class ItemDaoTest yang akan digunakan database, seperti yang ditunjukkan dalam contoh kode berikut:
import com.example.inventory.data.Item

private var item1 = Item(1, "Apples", 10.0, 20)
private var item2 = Item(2, "Bananas", 15.0, 97)
  1. Tambahkan fungsi utilitas untuk menambahkan satu item, lalu dua item, ke database. Anda akan menggunakan fungsi ini nanti, dalam pengujian. Tandai sebagai suspend agar dapat berjalan di coroutine.
private suspend fun addOneItemToDb() {
    itemDao.insert(item1)
}

private suspend fun addTwoItemsToDb() {
    itemDao.insert(item1)
    itemDao.insert(item2)
}
  1. Tulis pengujian untuk menyisipkan satu item ke dalam database, insert(). Beri nama pengujian daoInsert_insertsItemIntoDB dan anotasikan dengan @Test.
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Test


@Test
@Throws(Exception::class)
fun daoInsert_insertsItemIntoDB() = runBlocking {
    addOneItemToDb()
    val allItems = itemDao.getAllItems().first()
    assertEquals(allItems[0], item1)
}

Dalam pengujian ini, Anda menggunakan fungsi utilitas addOneItemToDb() untuk menambahkan satu item ke database. Kemudian, Anda akan membaca item pertama dalam database. Dengan assertEquals(), Anda membandingkan nilai yang diharapkan dengan nilai sebenarnya. Anda menjalankan pengujian dalam coroutine baru dengan runBlocking{}. Penyiapan ini adalah alasan Anda menandai fungsi utilitas sebagai suspend.

  1. Jalankan pengujian dan pastikan pengujian berhasil.

2f0ddde91781d6bd.png

8f66e03d03aac31a.png

  1. Tulis pengujian lain untuk getAllItems() dari database. Beri nama pengujian daoGetAllItems_returnsAllItemsFromDB.
@Test
@Throws(Exception::class)
fun daoGetAllItems_returnsAllItemsFromDB() = runBlocking {
    addTwoItemsToDb()
    val allItems = itemDao.getAllItems().first()
    assertEquals(allItems[0], item1)
    assertEquals(allItems[1], item2)
}

Dalam pengujian di atas, Anda menambahkan dua item ke database di dalam coroutine. Kemudian, Anda membaca kedua item tersebut dan membandingkannya dengan nilai yang diharapkan.

6. Menampilkan detail item

Dalam tugas ini, Anda akan membaca dan menampilkan detail entity di layar Item Details. Anda menggunakan status UI item, seperti nama, harga, dan jumlah dari database aplikasi inventaris, lalu menampilkannya di layar Item Details dengan composable ItemDetailsScreen. Fungsi composable ItemDetailsScreen ditulis sebelumnya untuk Anda dan berisi tiga composable Teks yang menampilkan detail item.

ui/item/ItemDetailsScreen.kt

Layar ini adalah bagian dari kode awal dan menampilkan detail item, yang akan Anda lihat di codelab berikutnya. Anda tidak mengerjakan layar ini dalam codelab ini. ItemDetailsViewModel.kt adalah ViewModel yang sesuai untuk layar ini.

de7761a894d1b2ab.png

  1. Dalam fungsi composable HomeScreen, perhatikan panggilan fungsi HomeBody(). navigateToItemUpdate diteruskan ke parameter onItemClick, yang akan dipanggil saat Anda mengklik item mana pun dalam daftar.
// No need to copy over 
HomeBody(
    itemList = homeUiState.itemList,
    onItemClick = navigateToItemUpdate,
    modifier = modifier
        .padding(innerPadding)
        .fillMaxSize()
)
  1. Buka ui/navigation/InventoryNavGraph.kt dan perhatikan parameter navigateToItemUpdate di composable HomeScreen. Parameter ini menentukan tujuan untuk navigasi sebagai layar detail item.
// No need to copy over 
HomeScreen(
    navigateToItemEntry = { navController.navigate(ItemEntryDestination.route) },
    navigateToItemUpdate = {
        navController.navigate("${ItemDetailsDestination.route}/${it}")
   }

Bagian dari fungsi onItemClick ini sudah diimplementasikan untuk Anda. Saat Anda mengklik item daftar, aplikasi akan membuka layar detail item.

  1. Klik item mana pun dalam daftar inventaris untuk melihat layar detail item dengan kolom kosong.

Layar detail item dengan data kosong

Untuk mengisi kolom teks dengan detail item, Anda harus mengumpulkan status UI di ItemDetailsScreen().

  1. Di UI/Item/ItemDetailsScreen.kt, tambahkan parameter baru ke composable ItemDetailsScreen jenis ItemDetailsViewModel dan gunakan metode factory untuk menginisialisasinya.
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.inventory.ui.AppViewModelProvider

@Composable
fun ItemDetailsScreen(
    navigateToEditItem: (Int) -> Unit,
    navigateBack: () -> Unit,
    modifier: Modifier = Modifier,
    viewModel: ItemDetailsViewModel = viewModel(factory = AppViewModelProvider.Factory)
)
  1. Dalam composable ItemDetailsScreen(), buat val yang disebut uiState untuk mengumpulkan status UI. Gunakan collectAsState() untuk mengumpulkan uiState StateFlow dan mewakili nilai terbarunya melalui State. Android Studio menampilkan error referensi yang belum terselesaikan.
import androidx.compose.runtime.collectAsState

val uiState = viewModel.uiState.collectAsState()
  1. Untuk mengatasi error, buat val yang disebut uiState dari jenis StateFlow<ItemDetailsUiState> di class ItemDetailsViewModel.
  2. Ambil data dari repositori item dan petakan ke ItemDetailsUiState menggunakan fungsi ekstensi toItemDetails(). Fungsi ekstensi Item.toItemDetails() sudah ditulis untuk Anda sebagai bagian dari kode awal.
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

val uiState: StateFlow<ItemDetailsUiState> =
         itemsRepository.getItemStream(itemId)
             .filterNotNull()
             .map {
                 ItemDetailsUiState(itemDetails = it.toItemDetails())
             }.stateIn(
                 scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
                 initialValue = ItemDetailsUiState()
             )
  1. Teruskan ItemsRepository ke ItemDetailsViewModel untuk mengatasi error Unresolved reference: itemsRepository.
class ItemDetailsViewModel(
    savedStateHandle: SavedStateHandle,
    private val itemsRepository: ItemsRepository
    ) : ViewModel() {
  1. Di ui/AppViewModelProvider.kt, update penginisialisasi untuk ItemDetailsViewModel seperti ditunjukkan dalam cuplikan kode berikut:
initializer {
    ItemDetailsViewModel(
        this.createSavedStateHandle(),
        inventoryApplication().container.itemsRepository
    )
}
  1. Kembali ke ItemDetailsScreen.kt dan perhatikan bahwa error dalam composable ItemDetailsScreen() telah diselesaikan.
  2. Pada composable ItemDetailsScreen(), update panggilan fungsi ItemDetailsBody() dan teruskan argumen uiState.value ke itemUiState.
ItemDetailsBody(
    itemUiState = uiState.value,
    onSellItem = {  },
    onDelete = { },
    modifier = modifier.padding(innerPadding)
)
  1. Amati implementasi ItemDetailsBody() dan ItemInputForm(). Anda meneruskan item yang saat ini dipilih dari ItemDetailsBody() ke ItemDetails().
// No need to copy over

@Composable
private fun ItemDetailsBody(
    itemUiState: ItemUiState,
    onSellItem: () -> Unit,
    onDelete: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
       //...
    ) {
        var deleteConfirmationRequired by rememberSaveable { mutableStateOf(false) }
        ItemDetails(
             item = itemDetailsUiState.itemDetails.toItem(), modifier = Modifier.fillMaxWidth()
         )

      //...
    }
  1. Jalankan aplikasi. Saat Anda mengklik elemen daftar pada layar Inventory, layar Item Details akan ditampilkan.
  2. Perhatikan bahwa layar tidak kosong lagi. Halaman ini menampilkan detail entity yang diambil dari database inventaris.

Layar detail item dengan detail item yang valid

  1. Ketuk tombol Sell. Tidak ada yang terjadi.

Di bagian berikutnya, Anda akan menerapkan fungsi tombol Sell.

7. Menerapkan layar Detail item

ui/item/ItemEditScreen.kt

Layar Item edit sudah disediakan untuk Anda sebagai bagian dari kode awal.

Tata letak ini berisi composable kolom teks untuk mengedit detail item inventaris baru.

Edit tata letak item dengan nama item harga dan jumlah item dalam kolom stok

Kode untuk aplikasi ini masih belum berfungsi sepenuhnya. Misalnya, di layar Item Details, saat Anda mengetuk tombol Sell, Quantity in Stock tidak akan berkurang. Saat Anda mengetuk tombol Delete, aplikasi akan memunculkan dialog konfirmasi. Namun, saat Anda memilih tombol Yes, aplikasi tidak benar-benar menghapus item.

pop-up konfirmasi penghapusan item

Terakhir, tombol FAB aad0ce469e4a3a12.png membuka layar Edit Item yang kosong.

Layar edit item dengan kolom kosong

Di bagian ini, Anda akan menerapkan fungsi tombol Sell, Delete, dan FAB.

8. Menerapkan item jual

Di bagian ini, Anda akan memperluas fitur aplikasi untuk mengimplementasikan fungsi penjualan. Update ini mencakup tugas-tugas berikut:

  • Tambahkan pengujian untuk fungsi DAO guna mengupdate entity.
  • Tambahkan fungsi di ItemDetailsViewModel untuk mengurangi jumlah dan mengupdate entity di database aplikasi.
  • Nonaktifkan tombol Sell jika jumlahnya nol.
  1. Di ItemDaoTest.kt, tambahkan fungsi bernama daoUpdateItems_updatesItemsInDB() tanpa parameter. Anotasi dengan @Test dan @Throws(Exception::class).
@Test
@Throws(Exception::class)
fun daoUpdateItems_updatesItemsInDB()
  1. Tentukan fungsi dan buat blok runBlocking. Panggil addTwoItemsToDb() di dalamnya.
fun daoUpdateItems_updatesItemsInDB() = runBlocking {
    addTwoItemsToDb()
}
  1. Perbarui kedua entity dengan nilai yang berbeda, yang memanggil itemDao.update.
itemDao.update(Item(1, "Apples", 15.0, 25))
itemDao.update(Item(2, "Bananas", 5.0, 50))
  1. Ambil entity dengan itemDao.getAllItems(). Bandingkan dengan entity yang diupdate dan nyatakan.
val allItems = itemDao.getAllItems().first()
assertEquals(allItems[0], Item(1, "Apples", 15.0, 25))
assertEquals(allItems[1], Item(2, "Bananas", 5.0, 50))
  1. Pastikan fungsi yang sudah selesai terlihat seperti berikut:
@Test
@Throws(Exception::class)
fun daoUpdateItems_updatesItemsInDB() = runBlocking {
    addTwoItemsToDb()
    itemDao.update(Item(1, "Apples", 15.0, 25))
    itemDao.update(Item(2, "Bananas", 5.0, 50))

    val allItems = itemDao.getAllItems().first()
    assertEquals(allItems[0], Item(1, "Apples", 15.0, 25))
    assertEquals(allItems[1], Item(2, "Bananas", 5.0, 50))