5.21

混沌武士丞發表於2024-06-19

3. 更新介面狀態

在此任務中,您將嚮應用新增一個 LazyColumn 來顯示儲存在資料庫中的資料。

顯示商品目錄中商品的手機螢幕

HomeScreen 可組合函式演示

  • 開啟 ui/home/HomeScreen.kt 檔案並檢視 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()
)
}

此可組合函式會顯示以下各項:

  • 帶有應用名稱的頂部應用欄
  • 用於向商品目錄中新增新商品的懸浮操作按鈕 (FAB) 7b1535d90ee957fa.png
  • HomeBody() 可組合函式

HomeBody() 可組合函式會根據傳入的列表顯示商品目錄商品。在起始程式碼實現中,我們將空列表 (listOf()) 傳遞給了 HomeBody() 可組合函式。如需將商品目錄列表傳遞給此可組合函式,您必須從儲存庫中檢索商品目錄資料,並將其傳入 HomeViewModel

HomeViewModel 中發出介面狀態

當您向 ItemDao 新增用於獲取商品的 getItem()getAllItems() 方法時,您將 Flow 指定為返回值型別。回想一下,Flow 代表通用資料流。透過返回 Flow,您只需在指定生命週期內明確呼叫 DAO 中的方法一次即可。Room 以非同步方式處理底層資料的更新。

從資料流中獲取資料的過程稱為收集資料流。從介面層中的資料流收集資料時,需要考慮一些事項。

  • 配置更改等生命週期事件(例如旋轉裝置)會導致重新建立 activity,進而導致重組,並從您的 Flow 重新收集資料。
  • 建議您將值快取為狀態,這樣現有資料就不會在生命週期事件之間丟失。
  • 如果沒有任何觀察器(例如在可組合項的生命週期結束後),則應取消資料流。

如需從 ViewModel 公開 Flow,推薦使用 StateFlow。無論介面生命週期如何,使用 StateFlow 均可儲存和觀察資料。如需將 Flow 轉換為 StateFlow,您可以使用 stateIn 運算子。

stateIn 運算子有三個引數,如下所述:

  • scope - viewModelScope 定義了 StateFlow 的生命週期。取消 viewModelScope 後,StateFlow 也會取消。
  • started - 僅當介面可見時,流水線才應有效。為此,請使用 SharingStarted.WhileSubscribed()。如需配置從最後一個訂閱者消失到停止共享協程之間的延遲時間(以毫秒為單位),請將 TIMEOUT_MILLIS 傳遞給 SharingStarted.WhileSubscribed() 方法。
  • initialValue - 將狀態流的初始值設定為 HomeUiState()

Flow 轉換為 StateFlow 後,您可以使用 collectAsState() 方法對其進行收集,並將其資料轉換為相同型別的 State

在此步驟中,您將檢索 Room 資料庫中的所有商品,作為介面狀態的 StateFlow 可觀察 API。當 Room Inventory 資料發生更改時,介面會自動更新。

  1. 開啟 ui/home/HomeViewModel.kt 檔案,其中包含一個 TIMEOUT_MILLIS 常量和一個 HomeUiState 資料類,該類將商品列表作為建構函式引數。
// 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. HomeViewModel 類中,宣告一個名為 homeUiState 且型別為 StateFlow<HomeUiState>val。您很快就要解決初始化錯誤。
val homeUiState: StateFlow<HomeUiState>
  1. itemsRepository 呼叫 getAllItemsStream(),並將其分配給您剛剛宣告的 homeUiState
val homeUiState: StateFlow<HomeUiState> =
itemsRepository.getAllItemsStream()

您現在會收到一個“Unresolved reference: itemsRepository”錯誤。如需解決“Unresolved reference”錯誤,您需要將 ItemsRepository 物件傳遞給 HomeViewModel

  1. 將型別為 ItemsRepository 的建構函式引數新增到 HomeViewModel 類中。
import com.example.inventory.data.ItemsRepository

class HomeViewModel(itemsRepository: ItemsRepository): ViewModel() {
  1. ui/AppViewModelProvider.kt 檔案的 HomeViewModel 初始化程式中,傳遞 ItemsRepository 物件,如下所示。
initializer {
HomeViewModel(inventoryApplication().container.itemsRepository)
}
  1. 返回至 HomeViewModel.kt 檔案。請注意型別不匹配錯誤。如需解決此問題,請新增轉換對映,如下所示。
val homeUiState: StateFlow<HomeUiState> =
itemsRepository.getAllItemsStream().map { HomeUiState(it) }

Android Studio 仍會顯示型別不匹配錯誤。導致此錯誤的原因在於,homeUiState 的型別為 StateFlow,而 getAllItemsStream() 卻返回了 Flow

  1. 使用 stateIn 運算子將 Flow 轉換為 StateFlowStateFlow 是介面狀態的可觀察 API,可讓介面自行更新。
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. 構建應用,確保程式碼中沒有錯誤。請注意,使用者介面不會有任何變化。