带有 Room 数据库查询和 Jetpack Compose 的 Android ViewModel

问题描述 投票:0回答:1

我正在开发一个具有典型列表和详细视图的应用程序。当我在详细信息表单中输入数据并保存时,列表不会刷新以包含新项目。这是一些代码来展示我在做什么:

列表屏幕:

@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
一起工作?

有关更多背景信息,可以在此处

找到完整的项目
android kotlin android-jetpack-compose android-room android-viewmodel
1个回答
0
投票

您不应该在视图模型中收集流(

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

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.