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()
)
- 构建应用,确保代码中没有错误。请注意,用户界面不会有任何变化。