我正在开发一个具有典型列表和详细视图的应用程序。当我在详细信息表单中输入数据并保存时,列表不会刷新以包含新项目。这是一些代码来展示我在做什么:
列表屏幕:
@Composable
fun ListScreen(
navController: NavController,
db: BaseballCardDatabase,
) {
val viewModel: ListViewModel =
viewModel(factory = ListViewModelFactory(db.baseballCardDao))
val cards by viewModel.baseballCards.collectAsStateWithLifecycle(initialValue = emptyList())
val stateList by remember {
derivedStateOf {
cards
.map { card ->
SelectedState(
card,
false
)
}
.toMutableStateList()
}
}
Scaffold(
floatingActionButton = { AddCardButton(navController) },
modifier = Modifier.fillMaxSize()
) { innerPadding ->
BaseballCardList(
navController = navController,
cards = stateList,
onCardChanged = { index, card -> stateList[index] = card },
)
}
}
列表视图模型:
class ListViewModel(val baseballCardDao: BaseballCardDao) : ViewModel() {
val filterState = MutableStateFlow(FilterState())
val isFiltered = mutableStateOf(false)
val baseballCards = MutableStateFlow<List<BaseballCard>>(emptyList())
init {
viewModelScope.launch {
filterState.collect { filter ->
baseballCards.value = getBaseballCards(
filter.brand,
filter.year,
filter.number,
filter.playerName,
filter.team
).first()
}
}
}
private fun getBaseballCards(
brand: String,
year: Int,
number: String,
playerName: String,
team: String,
): Flow<List<BaseballCard>> {
return baseballCardDao.getBaseballCards(
"%$brand%",
year,
"%$number%",
"%$playerName%",
"%$team%",
)
}
fun applyFilter(filter: FilterState) {
filterState.value = filter
isFiltered.value = filter != FilterState()
}
}
棒球卡道:
@Dao
interface BaseballCardDao {
@Insert
suspend fun insertBaseballCard(card: BaseballCard)
@get:Query("SELECT * FROM baseball_cards")
val baseballCards: Flow<List<BaseballCard>>
@Query(
"SELECT * FROM baseball_cards " +
"WHERE brand LIKE :brand " +
" AND (year = :year OR -1 = :year) " +
" AND number LIKE :number " +
" AND player_name LIKE :playerName " +
" AND team LIKE :team"
)
fun getBaseballCards(
brand: String,
year: Int,
number: String,
playerName: String,
team: String,
): Flow<List<BaseballCard>>
@Query("SELECT * FROM baseball_cards WHERE _id = :id")
suspend fun getBaseballCard(id: Long): BaseballCard
}
我最近添加了过滤逻辑。在此之前,我刚刚获得了
val baseballCards = baseballCardDao.baseballCards
,并且该列表按我的预期工作。当数据库发生任何更改时,它会重新组合,我认为是因为 Flow
提供了一个新的卡片列表供 UI 渲染。但现在我用过滤器参数调用 getBaseballCards()
,列表不会像我想要的那样重新组合。
我尝试过的一个解决方案是每次添加卡、删除卡或编辑卡时调用
getBaseballCards()
。但这使得我真的不需要 Flow
并且可以直接返回 List
。有没有办法让这个工作与 Flow
一起工作?
有关更多背景信息,可以在此处
找到完整的项目您不应该在视图模型中收集流(
first
是一个特殊的收集函数,它在收到第一个值后停止收集,因此得名)。相反,您应该只转换流并最终将它们公开为 StateFlow:
val baseballCards: StateFlow<List<BaseballCard>> = filterState
.flatMapLatest(::getBaseballCards)
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5.seconds),
initialValue = emptyList(),
)
我将
getBaseballCards
更改为这样以使其更易于使用:
private fun getBaseballCards(filter: FilterState): Flow<List<BaseballCard>> =
baseballCardDao.getBaseballCards(
"%${filter.brand}%",
filter.year,
"%${filter.number}%",
"%${filter.playerName}%",
"%${filter.team}%",
)
现在您可以删除整个
init
块。另外,您不再需要 initialValue
的 collectAsStateWithLifecycle
参数。
您的代码中还有其他一些看起来可疑的东西。例如,您永远不应该在视图模型中使用 MutableState。有一些令人讨厌的边缘情况,其行为并不像人们想象的那样。只需将其替换为
MutableStateFlow
即可。视图模型中的所有 MutableStateFlow 都应该是 private
,这样它们就不能从外部直接访问。公开一个函数,例如您的 applyFilter
。
另外
stateList
看起来它不会按预期工作。例如,当设备旋转时它会重置。此外,数据库中的新列表似乎用 false
覆盖了所有内容。当牌的顺序被修改时会发生什么?使用索引似乎是错误的。我认为这应该转移到视图模型中:
private val _selectedCards = MutableStateFlow(emptyList<Int>())
val selectedCards = _selectedCards.asStateFlow()
fun selectCard(card: BaseballCard) {
_selectedCards.update { it + card._id }
}
fun unSelectCard(card: BaseballCard) {
_selectedCards.update { it - card._id }
}
在您的可组合项中,只需照常收集
selectedCards
流并使用其内容来指示选择状态。如果您不想有两个不同的流程,请删除 selectedCards
并将 _selectedCards
合并到 baseballCards
中,如下所示:
val baseballCards: StateFlow<List<SelectedState>> = combine(
filterState.flatMapLatest(::getBaseballCards),
_selectedCards,
) { cards, selected ->
cards.map { card ->
SelectedState(
card,
selected.contains(card._id),
)
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5.seconds),
initialValue = emptyList(),
)
关于您的评论,如果您的 API 不支持开箱即用的流程,则只需
callbackFlow
。它连接了基于回调的 API 和 Flows 世界。由于您的 DAO 已经返回一个 Flow,因此不需要 callbackFlow
。