我使用preferences DataStore来持久化一组简单的CategoriesItemResponse
(我知道这不是一个好的做法,但这不是重点)。我在我的UserPreferencesRepository
中有一个Flow<Set<CategoriesItemResponse>>
,它通过各种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"
)
}
}
}
}
调用这些函数并公开流的视图模型如下:
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没有看到项目顺序的变化,除非我重新访问页面。
@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))
}
}
}
第一次访问编辑页面时,我们得到以下日志:
categories in UI = []
categories flow读取JSON in favorites flow =[{"id" 12,"isSuggested":真的,"name":"Confectionery","registrationType": 0},{"id" 13日"isSuggested":真的,"name":"肉及肉制品(禽肉除外)","注册类型":0}]
categories in UI = [actual-combined-list]
现在,在拖动项目并重新排序它们时,我们可以看到首选项被更新了,但是UI没有反映重新排序,除非我们重新访问页面。下面是拖动项目后的日志:
VM重新排序
更新了reOrder类别中的JSON:[{"id":13,"isSuggested":true,"name";肉类及肉制品(其他)。比家禽)","registrationType" 0},{"id" 12,"isSuggested":真的,"name":"Confectionery","registrationType": 0}]<——现在"肉……首先是
categories flow读取JSON in favorites flow =[{"id":13,"isSuggested":true,"name";肉类及肉制品(其他)。比家禽)","registrationType" 0},{"id" 12,"isSuggested":真的,"name":"Confectionery","registrationType": 0}]
变换称为
ui中没有"类别"这里是日志条目。我哪里搞砸了?
我猜你的问题来自collectAsState
,因为它只会触发对新的不同值的重组,使用相等检查。
即使Set
是可迭代的,并且一些实现仍然按照您创建它们的方式保持顺序,但这种顺序实际上并不是Set
契约的一部分。
"Reordered"set是相等的,所以不会触发new state。
如果项目安排是重要的,userSavedCategories
必须产生索引:一个List
。