Jetpack Compose - Accompanist HorizontalPager中的数据惰性加载

4
问题非常简单,我不知道为什么以前没有人提出过。
我必须从数据库查询大量数据,然后逐个在HorizontalPager中显示。如果一次加载所有数据,在组合HorizontalPager之前,它需要太长时间来加载,用户必须长时间看到CircularProgressIndicator
我只想加载用户可见的HorizontalPager页面的数据。所以应该像这样进行:
  • 在索引=0时,仅加载第0页的数据,并在加载时显示CircularProgressIndicator
  • 在索引=1时,仅加载第1页的数据,并在加载时显示CircularProgressIndicator
等等。
我该如何实现?
以下是我的代码,但无法正常工作:
val map by viewModel.statementWithTransMap.collectAsStateWithLifecycle()

HorizontalPager(
    modifier = Modifier.fillMaxSize(),
    state = horizontalPagerState,
    count = statementIds.size
) { index ->

    LaunchedEffect(Unit) {
        val id = statementIds[index]
        viewModel.loadStatementByStatementId(index, id)
    }

    val item = map[index]

    if (item == null) {
        Box(modifier = Modifier.fillMaxSize()) {
            CircularProgressIndicator(
                modifier = Modifier.align(Alignment.Center)
            )
        }
        return@HorizontalPager
    }

    Page(item)
}

我的视图模型中相关的代码是:

fun loadStatementByStatementId(index: Int, statementId: Int) {

    viewModelScope.launch {
        getStatementsWithTranslationByStatementIdUseCase(statementId).collect {

            loadedStatements[index] = it
            _statementWithTransMap.value = loadedStatements
        }
    }
}

我认为你提出的解决方案也不是最好的,因为你的用户在每个页面上都会看到进度指示器,即使只有一瞬间。最好的解决方案是使用类似于AndroidX分页库的方式加载一些数据块(例如每次20个项目)。使用分页库,您将获得开箱即用的功能。 - Jan Bína
看到进度指示器只闪现一瞬间并不是问题所在。除了分页,您有没有其他解决方案? - Bugs Happen
你可以在每个页面中使用LaunchedEffect,并在那里加载或调用一些视图模型方法来进行加载。 - Jan Bína
我已经尝试过了,但在我的端上没有起作用。你能否分享一个示例代码作为答案? - Bugs Happen
1个回答

1

我建议使用androidx分页,但如果您不想使用,类似这样的代码也应该可以工作。为了简单起见,我把所有的代码都放在了compose代码里面,但最好在ViewModel或类似的地方处理加载。

@Composable fun MyPager(db: Database) {
    val loadedData = remember {
        mutableStateMapOf<Int, Item>()
    }
    HorizontalPager(count = 100) { index ->
        LaunchedEffect(Unit) {
            if (loadedData[index] == null) {
                loadedData[index] = db.getItem(index)
            }
        }

        val item = loadedData[index]
        if (item == null) {
            CircularProgressIndicator()
        } else {
            ItemContent(item)
        }
    }
}

根据所提供的代码进行编辑: 好的,假设loadedStatements是一个MutableMap,而_statementWithTransMap是一个StateFlow,我认为问题出在这里:
loadedStatements[index] = it
_statementWithTransMap.value = loadedStatements

您更新了可变映射并将其设置为StateFlow,但是相同的映射已经是该StateFlow的值,并且当新值相同时它不会发出(它不关心映射的内容,对象是相同的)。您需要创建一个新的映射,类似于以下内容:

loadedStatements = loadedStatements.toMutableMap()
loadedStatements[index] = it
_statementWithTransMap.value = loadedStatements

你尝试运行过这段代码吗?在你那边它是否完美地工作了? - Bugs Happen
不,但我不明白为什么它不能工作。如果你尝试了类似这样的东西,最好发布你的代码以及它为什么不能工作。 - Jan Bína
我的代码几乎和那个一模一样,但是它不起作用。在 loadedData[index] = db.getItem(index) 之后,它没有重新组合。让我把我的代码添加到问题中。 - Bugs Happen
有趣的,让我试试看。 - Bugs Happen
1
是的,就是这个问题。现在它正常工作了。感谢您的帮助和时间。 - Bugs Happen
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接