android Flow〈Set< CategoriesItemResponse>>在重新排序内容后似乎不会发出新的转换值

7fhtutme  于 2023-04-04  发布在  Android
关注(0)|答案(1)|浏览(78)

我使用偏好数据存储来持久化一个简单的CategoriesItemResponse集合(我知道这不是一个好的实践,但这不是重点)。我在UserPreferencesRepository中有一个Flow<Set<CategoriesItemResponse>>,它通过各种ViewModel公开以供观察。
此外,我还提供了在Set中添加、删除和重新排序类别项的方法,以便每个操作都反映在流中。添加/删除功能似乎工作正常,观察流的屏幕确实对数据的变化做出了即时React。尽管重新排序逻辑的流程似乎有缺陷。
这是我的repository:

class UserPreferencesRepository(private val context: Context) {
    val userFavoriteCategories = context.dataStore.data.map { prefs ->
        val categoriesJson = prefs[PreferenceKeys.userFavoriteCategories]
        println("categories flow read JSON in favorites flow = $categoriesJson")
        categoriesJson?.convertToSetObject<CategoriesResponseItem>() ?: emptySet()
    }

    suspend fun addCategoryToUserFavorites(category: CategoriesResponseItem) {
        withContext(Dispatchers.IO) {
            try {
                context.dataStore.edit { preferences ->
                    val categoriesJson = preferences[PreferenceKeys.userFavoriteCategories]
                    val categories: Set<CategoriesResponseItem> =
                        categoriesJson?.convertToSetObject() ?: emptySet()

                    val updatedCategories =
                        categories.toMutableSet().apply { add(category) }.toSet()

                    val updatedJSON = Gson().toJson(updatedCategories)
                    println("Updated JSON in add category: $updatedJSON")
                    preferences[PreferenceKeys.userFavoriteCategories] = updatedJSON
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e(
                    UserPreferencesRepository::class.java.simpleName,
                    "Failed to add category $category to user preferences!"
                )
            }
        }
    }

    suspend fun removeCategoryFromUserFavorites(category: CategoriesResponseItem) {
        withContext(Dispatchers.IO) {
            try {
                context.dataStore.edit { preferences ->
                    val categoriesJson = preferences[PreferenceKeys.userFavoriteCategories]
                    val categories: Set<CategoriesResponseItem> =
                        categoriesJson?.convertToSetObject() ?: emptySet()

                    val updatedCategories =
                        categories.toMutableSet().apply { remove(category) }.toSet()

                    val updatedJSON = Gson().toJson(updatedCategories)
                    println("updated JSON in remove category: $updatedJSON")
                    preferences[PreferenceKeys.userFavoriteCategories] = updatedJSON
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e(
                    UserPreferencesRepository::class.java.simpleName,
                    "Failed to remove category $category to user preferences!"
                )
            }
        }
    }

    suspend fun reOrderCategories(from: Int, to: Int) {
        withContext(Dispatchers.IO) {
            try {
                context.dataStore.edit { preferences ->
                    val categoriesJson = preferences[PreferenceKeys.userFavoriteCategories]
                    val categories: Set<CategoriesResponseItem> =
                        categoriesJson?.convertToSetObject() ?: emptySet()

                    val updatedCategories =
                        categories.toMutableList().apply {
                            add(to, removeAt(from))
                        }.toSet()

                    val updatedJSON = Gson().toJson(updatedCategories)
                    println("updated JSON in reOrder categories: $updatedJSON")
                    preferences[PreferenceKeys.userFavoriteCategories] = updatedJSON
                }
            } catch (e: Exception) {
                e.printStackTrace()
                Log.e(
                    UserPreferencesRepository::class.java.simpleName,
                    "Failed to persist reordering from $from to $to for user categories"
                )
            }
        }
    }
}

调用这些函数并公开流的视图模型如下所示:

class CategoriesViewModel(private val userPreferencesRepository: UserPreferencesRepository) :
    ViewModel() {
    //todo:sp consider getting these dynamically
    private val hardcodedCategories = listOf(
        CategoriesResponseItem(
            id = -1,
            name = "FRUITS AND VEGETABLES",
            registrationType = CategoryType.PREMIUM.ordinal,
            isSuggested = false
        ),
        CategoriesResponseItem(
            id = -2,
            name = "NUTS, NUT PRODUCTS AND SEEDS",
            registrationType = CategoryType.PREMIUM.ordinal,
            isSuggested = false
        ),
        CategoriesResponseItem(
            id = -3,
            name = "FRUITS AND VEGETABLES",
            registrationType = CategoryType.PREMIUM.ordinal,
            isSuggested = false
        ),
        CategoriesResponseItem(
            id = -4,
            name = "FRUITS AND VEGETABLES",
            registrationType = CategoryType.PREMIUM.ordinal,
            isSuggested = false
        )
    )

    private var backingCategories = emptyList<CategoriesResponseItem>()

    val userSavedCategories = userPreferencesRepository.userFavoriteCategories.transform {
        val mutableCategories = it.toMutableSet()
        mutableCategories.addAll(hardcodedCategories)
        backingCategories = mutableCategories.toList()
        println("transform called")
        emit(mutableCategories.toSet())
    }

    fun removeCategory(category: CategoriesResponseItem) {
        viewModelScope.launch(Dispatchers.IO) {
            userPreferencesRepository.removeCategoryFromUserFavorites(category)
        }
    }

    fun onItemReorder(from: ItemPosition, to: ItemPosition) {
        viewModelScope.launch(Dispatchers.IO) {
            println("reorder in VM called")
            userPreferencesRepository.reOrderCategories(from.index, to.index)
        }
    }

    fun isCategoryDraggable(draggedOver: ItemPosition, dragging: ItemPosition): Boolean {
        return backingCategories
            .getOrNull(draggedOver.index)?.registrationType == CategoryType.FREE.ordinal
    }
}

我的问题似乎是UI似乎没有对重新排序做出React,尽管调用了存储库的reorder方法并更新了首选项。
transform()用于将用户的数据与一些“高级”类别样本组合在一起,这些样本只在前端需要。Logcat似乎表明,该流程一直工作到转换lambda中的println("transform called"),但是UI看不到项目顺序的变化,除非我重新访问页面。
下面是UI代码:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun EditCategoriesScreen(
    navController: NavController,
    categoriesViewModel: CategoriesViewModel
) {
    val userCategories by categoriesViewModel.userSavedCategories.collectAsState(initial = emptySet())
    val reorderState = rememberReorderableLazyListState(
        onMove = { from, to ->
            categoriesViewModel.onItemReorder(from, to)
        },
        canDragOver = categoriesViewModel::isCategoryDraggable
    )

    Column(Modifier.fillMaxSize()) {
        Column(
            Modifier.weight(1f)
        ) {
            SearchFieldAsButton(modifier = Modifier.padding(top = 25.dp)) {
                navController.navigate(Screens.ADD_CATEGORIES.navRoute)
            }

            LazyColumn(
                state = reorderState.listState,
                modifier = Modifier
                    .padding(vertical = 16.dp)
                    .reorderable(reorderState)
                    .animateContentSize(),
                verticalArrangement = Arrangement.spacedBy(15.dp),
                userScrollEnabled = true,
            ) {
                println("categories in UI = ${userCategories.toList()}")
                itemsIndexed(
                    userCategories.toList(),
                    key = { _, item -> item.hashCode() }) { index, category ->

                    if (CategoryType.values()[category.registrationType] == CategoryType.FREE) {
                        ReorderableItem(
                            reorderableState = reorderState,
                            key = category.hashCode()
                        ) {
                            SwipeableUnlockedCategoryItem(
                                modifier = Modifier
                                    .animateItemPlacement()
                                    .padding(horizontal = 12.dp)
                                    .detectReorderAfterLongPress(reorderState),
                                displayNumber = 0,
                                percentageNumber = 0,
                                categoryName = category.name,
                                onDelete = {
                                    categoriesViewModel.removeCategory(category)
                                }
                            )
                        }
                    } else {
                        val isComingSoonItem =
                            index in (userCategories.size - 2 until userCategories.size)

                        LockedCategoryItem(
                            modifier = Modifier
                                .animateItemPlacement()
                                .padding(horizontal = 12.dp),
                            displayNumber = 0,
                            percentageNumber = 0,
                            categoryName = category.name,
                            isComingSoon = isComingSoonItem
                        )
                    }

                }
            }
        }

        Column(
            modifier = Modifier
                .wrapContentHeight()
                .background(
                    brush = Brush.verticalGradient(
                        endY = 90f,
                        colors = listOf(
                            colorResource(id = R.color.white).copy(alpha = 0.2f),
                            colorResource(id = R.color.white)
                        )
                    )
                ),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Spacer(Modifier.height(24.dp))
            Text(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(start = 42.dp, end = 46.dp),
                text = stringResource(id = R.string.sample_text),
                textAlign = TextAlign.Center,
                style = MaterialTheme.typography.bodySmall
            )

            Spacer(Modifier.height(16.dp))

            FoodakaiButton(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(50.dp)
                    .padding(start = 16.dp, end = 16.dp),
                text = stringResource(R.string.more_insights_text),
                fontSize = 18.sp
            ) {
                navController.navigate(Screens.MORE_INSIGHTS.navRoute)
            }

            Spacer(Modifier.height(8.dp))

            Image(
                modifier = Modifier
                    .padding(top = 14.dp, bottom = 4.dp)
                    .clickable {
                        navController.navigate(Screens.ADD_CATEGORIES.navRoute)
                    },
                painter = painterResource(id = R.drawable.plus_icon),
                alignment = Alignment.Center,
                contentDescription = stringResource(R.string.add_category_icon_content_desc)
            )

            Text(
                text = stringResource(R.string.add_a_category_text),
                style = MaterialTheme.typography.labelMedium
            )
            Spacer(modifier = Modifier.height(24.dp))
        }
    }
}

第一次访问编辑页面时,我们会得到以下日志:
UI中的类别= []
分类流读取收藏夹中的JSON流= [{“id”:12,“isSuggested”:true,“name”:“Confectionery”,“registrationType”:0},{“id”:13,“isSuggested”:true,“name”:“Meat And Meat Products(Other Than Poultry)",“registrationType”:0}]
UI中的类别= [actual-combined-list]
现在,在拖动项目并重新排序后,我们可以看到首选项被更新,但UI并没有反映重新排序,除非我们重新访问页面。以下是拖动项目后的日志:
调用了虚拟机中的重新排序
reOrder类别中更新的JSON:[{“id”:13,“isSuggested”:true,“name”:“肉类及肉制品(家禽除外)",“registrationType”:0},{“id”:12,“isSuggested”:true,“name”:“糖果”,“registrationType”:0}]〈---现在“肉类...”是第一个
分类流读取收藏夹中的JSON流= [{“id”:13,“isSuggested”:true,“name”:“肉类和肉类产品(家禽除外)",“registrationType”:0},{“id”:12,“isSuggested”:true,“name”:“糖果”,“registrationType”:0}]
转换调用
这里没有“UI中的类别”日志条目。我在哪里搞砸了?

pgvzfuti

pgvzfuti1#

我猜你的问题来自collectAsState,因为它只会触发新的不同值的重组,这是使用相等性检查的。
即使Set是可迭代的,并且一些实现仍然按照您创建它们的方式进行排序,但这种顺序实际上并不是Set契约的一部分。
“重新排序”的集合是相等的,因此不发出新状态。
如果项目排列很重要,userSavedCategories必须生成索引:a List .

相关问题