我正在使用首选项数据存储来保存一组简单的
CategoriesItemResponse
(我知道这不是一个好的做法,但这不是重点)。我的 Flow<Set<CategoriesItemResponse>>
中有一个 UserPreferencesRepository
,它通过各种 ViewModels 公开以供观察。
此外,我还提供了在 Set 中添加、删除和重新排序类别项的方法,以便每个操作都反映在流中。添加/删除功能似乎工作正常,观察流程的屏幕确实会立即对数据的变化做出反应。不过,重新排序逻辑的流程似乎有缺陷。
这是我的存储库:
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"
)
}
}
}
}
调用这些函数同时暴露流程的viewmodel是这样的:
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 似乎没有对重新排序做出反应,尽管调用了存储库的重新排序方法并且更新了首选项。
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 中的类别 = []
category flow read JSON in favorites flow = [{"id":12,"isSuggested":true,"name":"糖果","registrationType":0},{"id":13,"isSuggested":true,"name":"肉 和肉类产品(家禽除外)","registrationType":0}]
UI 中的类别 = [实际组合列表]
现在拖动项目并重新排序后,我们可以看到首选项已更新,但 UI 不会反映重新排序,除非我们重新访问页面。以下是拖动项目后的日志:
在 VM 中重新排序
更新了 reOrder 类别中的 JSON: [{"id":13,"isSuggested":true,"name":"肉类和肉类产品(其他 比 家禽)","registrationType":0},{"id":12,"isSuggested":true,"name":"Confectionery","registrationType":0}] <--- now "Meat..." is first
category flow read JSON in favorites flow = [{"id":13,"isSuggested":true,"name":"肉类和肉类产品(其他 比 家禽)","registrationType":0},{"id":12,"isSuggested":true,"name":"Confectionery","registrationType":0}]
转换称为
这里没有“UI 中的类别”日志条目。我哪里搞砸了?
我猜你的问题来自
collectAsState
,因为它只会触发对使用相等性检查的新的不同值的重组。
尽管
Set
s 是可迭代的,并且某些实现确实保留了您使用该顺序创建它们的顺序,但实际上并不是 Set
合同的一部分。 “重新排序”的集合是相等的,因此不会发出新状态。
如果项目安排很重要
userSavedCategories
必须产生一些索引:aList
.