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)
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 資料發生更改時,介面會自動更新。
- 開啟
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())
- 在
HomeViewModel
類中,宣告一個名為homeUiState
且型別為StateFlow<HomeUiState>
的val
。您很快就要解決初始化錯誤。
val homeUiState: StateFlow<HomeUiState>
- 對
itemsRepository
呼叫getAllItemsStream()
,並將其分配給您剛剛宣告的homeUiState
。
val homeUiState: StateFlow<HomeUiState> =
itemsRepository.getAllItemsStream()
您現在會收到一個“Unresolved reference: itemsRepository”錯誤。如需解決“Unresolved reference”錯誤,您需要將 ItemsRepository
物件傳遞給 HomeViewModel
。
- 將型別為
ItemsRepository
的建構函式引數新增到HomeViewModel
類中。
import com.example.inventory.data.ItemsRepository
class HomeViewModel(itemsRepository: ItemsRepository): ViewModel() {
- 在
ui/AppViewModelProvider.kt
檔案的HomeViewModel
初始化程式中,傳遞ItemsRepository
物件,如下所示。
initializer {
HomeViewModel(inventoryApplication().container.itemsRepository)
}
- 返回至
HomeViewModel.kt
檔案。請注意型別不匹配錯誤。如需解決此問題,請新增轉換對映,如下所示。
val homeUiState: StateFlow<HomeUiState> =
itemsRepository.getAllItemsStream().map { HomeUiState(it) }
Android Studio 仍會顯示型別不匹配錯誤。導致此錯誤的原因在於,homeUiState
的型別為 StateFlow
,而 getAllItemsStream()
卻返回了 Flow
。
- 使用
stateIn
運算子將Flow
轉換為StateFlow
。StateFlow
是介面狀態的可觀察 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()
)
- 構建應用,確保程式碼中沒有錯誤。請注意,使用者介面不會有任何變化。