我想使用Kotlin流从rest endpoint异步加载多个数据,并将其显示在回收器视图中。数据定义为ServiceDto
,ProductDto
,CategoryDto
。ProductDto有一个引用CategoryDto的categoryId
属性。
每个DTO都应该异步加载和显示,但CategoryDto必须在ProductDto之前完成,因为我需要类别名称+依赖产品并将其添加到mutableListOf<Any>
。回收器视图将显示类别名称,然后显示依赖产品。
产品的存储库实施
fun getProducts(networkId: String) = flow {
request.getAll("network/${networkId}/products", null)
.flowOn(Dispatchers.IO)
.onStart { emit(ApiResult.Loading) }
.catch { emit(ApiResult.Error(it.message.toString())) }
.map { ApiResult.Success(it) }
.also { emitAll(it) }
}
这是将ServiceDto加载到
private fun loadServices() {
lifecycleScope.launch {
async {
viewModel.getServices(baseViewModel.profile?.networkId ?: "")
}.await().onEach { result ->
when (result) {
is ApiResult.Loading -> {} // Handle loading
is ApiResult.Error -> {} // Handle error
is ApiResult.Success -> createServiceChips(result.data) // Create UI elements
}
}.collect()
}
}
这是加载产品和类别的实现
private fun loadCategoriesAndProducts() {
lifecycleScope.launch {
val products = async {
viewModel.getProducts(networkId).shareIn(lifecycleScope, SharingStarted.WhileSubscribed(), 1 // currently no idea what this means ) // Convert to hotflow to start loading data
}.await()
async { viewModel.getCategories(networkId) }.await().onEach { categories->
when (categories) {
is ApiResult.Loading -> {} // Handle loading
is ApiResult.Error -> {} // Handle error
is ApiResult.Success -> {
publishCategoryUI(categories.data)
collectProducts(products, categories.data)
}
}
}.collect()
}
}
这就是我收集产品并将类别和产品Map到平面列表的方式。
private suspend fun collectProducts(products: SharedFlow<ApiResult<List<ProductDto>>>, categories: List<CategoryDto>?) = coroutineScope {
products.onEach{ productResult ->
when (productResult) {
is ApiResult.Success -> {
val productCategoryList = mutableListOf<Any>()
withContext(Dispatchers.IO) {
categories?.forEach { category ->
productCategoryList.add(category.name)
productResult.data?.filter { product ->
product.categoryId == category.id
}.let {
productCategoryList.addAll(it?.toList() ?: emptyList())
}
}
}
productsAdapter.updateData(productCategoryList) {
loadingIndicator.visibility = View.INVISIBLE
}
}
is ApiResult.Error -> // Handle error
is ApiResult.Loading -> {} // Handle loading
}
}.collect()
}
一切正常,但我可以看到,当产品被添加到recyclerview,它阻止用户界面很短的时间。加载指示器是滞后之前,产品被显示。(只有13产品)
我如何改进实现,或者它是否正确?Kotlin协程和流提供了如此多的可能性,这使得有时很难找到一个好的/正确的解决方案。
1条答案
按热度按时间nzk0hqpo1#
你正在做的不正确的事情并没有害处,但只是让你的代码变得不必要的复杂:
getProducts
函数中,您不需要将流 Package 在emitAll
的flow
构建器中。删除外部flow { }
Package 器和.also { emitAll(it) }
。async { }.await()
模式。这和直接调用lambda中的代码没有什么不同。当编译器检测到你这样做时,它应该会发出警告。onEach { }.collect()
模式可以缩短为.collect { }
,或者按照我的喜好,collect()
可以变为launchIn(scope)
并替换外部的scope.launch
以减少代码嵌套/缩进。collectProducts()
中,您使用coroutineScope
创建了一个作用域,但从未使用它来运行任何子协程,因此这是毫无意义的。flowOn
,因为它们正确地封装了任何阻塞工作。公共流不应该阻塞它们的下游收集器。同样,你不应该需要withContext
来调用这些库中的suspend函数,因为按照惯例,suspend函数应该永远不会阻塞。你正在做的不正确和有害的事情:
loadCategoriesAndProducts()
中,您将SharedFlow传递给另一个收集它的函数。这是创建两个流的乘法,随着项目的到达,它将呈指数级增长。这可以更简单地解决,如下所示。您需要做的只是将两个流合并。repeatOnLifecycle
或flowWithLifecycle
,以避免在Activity处于屏幕外时收集,因为这会浪费资源。categories?.forEach {
块中,你正在做不必要的嵌套迭代。如果你想使用SharedFlow,这可以避免导致新的请求必须重新生成,那么你应该在你的ViewModel中放置一个函数,将
networkID
馈送到一个MutableShateFlow中,作为另一个SharedFlow的基础。你可以在这里私下声明你的两个并行流,然后公开合并它们。然后在您的活动中:
除此之外,
MutableList<Any>
的使用是一种代码气味,会产生代码可维护性问题。