我用
viewmodel
和 Flow
做了一个 Room
,将 UiState
上的变量与 Room
使用 .first()
连接起来,正如代码实验室向我展示的那样。我注意到使用 .first()
不是一个好主意,因为当我在数据库中插入某些内容时,屏幕不会自动刷新以显示新值。所以我了解到使用collect{}
是自动不断更新uistate变量的正确方法。现在更改后,我的屏幕没有显示任何数据,我无法找出原因。请问您能帮我找出问题出在哪里吗?我在这里添加viewmodel
。
你可以看到旧代码的注释。我的意思是带有
.first()
的代码可以工作,但不会自动更新。注释的代码后面是带有 collect{}
的新代码,但根本不起作用。
data class UiState(
val searchResult: List<Airport> = listOf(),
val favorites: List<Favorite> = listOf(),
val selectedAirport: Airport? = null,
val flightsForSelectedAirport: List<Airport> = listOf()
)
class FlightsScreenViewModel(
private val flightRepository: FlightsRepository,
private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() {
var uiState by mutableStateOf(UiState())
private set
var searchText by mutableStateOf("")
private set
init {
viewModelScope.launch {
// searchText = userPreferencesRepository.searchText.first()
userPreferencesRepository.searchText.collect{ searchText = it }
updateSearchResults()
}
}
private suspend fun updateSearchResults() {
// val searchResult = if (searchText != "")
// flightRepository.getAirportsByIatOrName(searchText).filterNotNull().first()
// else
// emptyList()
//
// val favorites = flightRepository.getFavorites().filterNotNull().first()
//
// uiState = uiState.copy(
// searchResult = searchResult,
// favorites = favorites
// )
if (searchText != "") {
flightRepository.getAirportsByIatOrName(searchText).filterNotNull().collect{ uiState = uiState.copy(searchResult = it) }
} else {
uiState = uiState.copy(favorites = emptyList())
}
flightRepository.getFavorites().filterNotNull().collect{ uiState = uiState.copy(favorites = it) }
}
fun updateSearchText(searchText: String) {
this.searchText = searchText
viewModelScope.launch {
userPreferencesRepository.saveSearchTextPreference(searchText)
updateSearchResults()
}
}
fun selectAirport(airport: Airport?) {
viewModelScope.launch {
// val flightsForSelectedAirport = if (airport == null) {
// emptyList()
// } else {
// flightRepository.getAllDifferentAirports(airport.id).first()
// }
//
// uiState = uiState.copy(
// selectedAirport = airport,
// flightsForSelectedAirport = flightsForSelectedAirport
// )
if (airport != null) {
flightRepository.getAllDifferentAirports(airport.id).collect{ uiState = uiState.copy(flightsForSelectedAirport = it) }
} else {
uiState = uiState.copy(flightsForSelectedAirport = emptyList())
}
uiState = uiState.copy(selectedAirport = airport)
}
}
fun insertFavorite(depart: String, arrive: String) {
if (!uiState.favorites.checkIfFavoriteExists(depart, arrive)) {
val favorite = Favorite(departureCode = depart, destinationCode = arrive)
viewModelScope.launch {
flightRepository.insertFavorite(favorite)
}
}
}
companion object {
val factory : ViewModelProvider.Factory = viewModelFactory {
initializer {
FlightsScreenViewModel(
FlightSearchApplication().container.flightRepository,
FlightSearchApplication().container.userPreferencesRepository
)
}
}
}
}
fun List<Favorite>.checkIfFavoriteExists(depart: String, arrive: String): Boolean{
for (favorite in this){
if (favorite.departureCode == depart && favorite.destinationCode == arrive)
return true
}
return false
}
这是应该在屏幕上显示内容的代码,但从
.first()
更新到 .collect{}
后没有显示任何内容
LazyColumn(
modifier = Modifier.padding(8.dp)
) {
items(uiState.searchResult) { airport ->
AirportDetail(airport, onAirportSelected)
}
}
uiState.selectedAirport?.let {
FlightsForAirport(
airport = uiState.selectedAirport,
arrivals = uiState.flightsForSelectedAirport,
favorites = uiState.favorites,
onFavoriteSelected = onFavoriteSelected
)
}
代码实验室已经过时了。应该这样做:
collect
它们。这就是你的视图模型的样子:
class FlightsScreenViewModel(
private val flightRepository: FlightsRepository,
private val userPreferencesRepository: UserPreferencesRepository,
) : ViewModel() {
private val searchResult: Flow<List<Airport>> =
userPreferencesRepository.searchText.flatMapLatest {
if (it.isEmpty()) flowOf(emptyList())
else flightRepository.getAirportsByIatOrName(it).filterNotNull()
}
private val favorites: Flow<List<Favorite>> =
flightRepository.getFavorites().filterNotNull()
private val selectedAirport = MutableStateFlow<Airport?>(null)
private val flightsForSelectedAirport: Flow<List<Airport>> =
selectedAirport.flatMapLatest {
if (it == null) flowOf(emptyList())
else flightRepository.getAllDifferentAirports(it.id)
}
val uiState: StateFlow<UiState> = combine(
searchResult,
favorites,
selectedAirport,
flightsForSelectedAirport,
::UiState,
).stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = UiState(),
)
fun updateSearchText(searchText: String) {
viewModelScope.launch {
userPreferencesRepository.saveSearchTextPreference(searchText)
}
}
fun selectAirport(airport: Airport?) {
selectedAirport.value = airport
}
fun insertFavorite(depart: String, arrive: String) {
val favorite = Favorite(departureCode = depart, destinationCode = arrive)
viewModelScope.launch {
flightRepository.insertFavorite(favorite)
}
}
companion object { /* ... */ }
}
流量永远不会被收集,它们只是被转换。例如,新转换的
searchResult
流是通过使用 userPreferencesRepository.searchText
流并将其映射到 flightRepository.getAirportsByIatOrName
流创建的。
另一方面,新的
flightsForSelectedAirport
流程基于新的 MutableStateFlow<Airport?>
,用于存储当前选定的机场。它的使用方式与 MutableState(*) 类似,因为它的 value
属性可以直接设置,如函数 fun selectAirport()
中所示。但它仍然是一个流,所以这里可以将其转换为Flow<List<Airport>>
。
现在创建
UiState
所需的所有值都存在于流中,这些流可以 combine
合并为单个流。然后将该流转换为 StateFlow(这与 MutableStateFlow 相关,与 Compose State 无关)并公开公开。
视图模型的任何使用者现在都可以收集该流以获得始终最新的 UiState。在您的可组合项中使用它:
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
这是 Kotlin Flows 世界和 Compose State 世界之间的桥梁。它将 StateFlow 转换为 Compose State 对象。通过使用
by
委托,uiState
实际上是 UiState
类型,其余代码的工作方式将与之前相同。
最后的想法:
您将
filterNotNull()
应用于 getAirportsByIatOrName
和 getFavorites
流。这意味着流不仅可以包含列表,还可以包含null
。这似乎不对,它们应该包含 emptyList()
而不是 null
。那么你应该删除filterNotNull()
。
我从
checkIfFavoriteExists
中删除了insertFavorite
。这应该在存储库中完成。数据的一致性不应该依赖于视图模型的作用,这是存储库的责任(或者甚至是数据源的责任,如果您将其分开的话)。理想情况下,这应该在数据包装到流中之前完成。这一切都取决于实际如何实施。fun List<Favorite>.checkIfFavoriteExists(
depart: String,
arrive: String,
): Boolean = any {
it.departureCode == depart && it.destinationCode == arrive
}
您可以使用像Hilt这样的依赖注入框架来自动化它,而不是编写显式视图模型工厂。
(*):虽然
MutableStateFlow
和MutableState
具有相似的名称,但它们是完全不同的类型,完全不相关。前者是 Kotlin 标准库的一部分,后者是 Compose 框架的一部分。它们共享一些语义,因为它们都存储可通过 value
属性访问的可观察值,但仅此而已。