如何在 MVVM android 应用程序中使用存储库模式和实时数据正确排序列表?
how to properly sort a list using repository pattern and live data in an MVVM android application?
我正在为 android 编写一个简单的待办事项列表样式应用程序,但我在添加排序功能时遇到了问题。
我有一个屏幕 RecyclerView
显示属于用户的所有列表,我想添加一个允许用户打开或关闭排序的功能。问题是我似乎无法找到一种方法来使显示给用户的列表在进行另一次更改后保持一致排序。
假设列表保存在 Room
数据库中。
实现我想要的最简单和最一致的方法是对数据库本身中的数据进行排序,但这可能不是可行的方法,因为它实现起来会很复杂(如果可能的话)。
很可能我应该在我的视图模型中对数据进行本地排序。我想我可以使用 MediatorLiveData
来做到这一点,它将观察数据库,而主机 activity 本身也会观察到它。在更新 MediatorLiveData
之前,我会使用标志 isSorting
对列表进行排序,以按原样 return 数据或在交付结果之前对其进行排序。
我的问题的关键是我希望能够打开和关闭此功能,同时在之后(执行另一项操作时)仍然保持数据的顺序。这是典型事件流的示例:
- 用户打开应用程序和数据库returns:“列表 3”、“列表 1”、“列表 2”。
- 数据按原样显示给用户(未排序)
- 用户开启排序功能
- 视图模型在本地对支持列表进行排序
- activity中的观察者收到排序后的列表
- activity 现在显示“列表 1”、“列表 2”、“列表 3”
- 用户添加了一个新列表:“列表 4”。
在进入问题部分之前,这里是一些代码:
存储库(为简洁起见缩短):
class ListsRepository {
val db = Database().getInstance();
fun getAllUserLists(userId: Int): LiveData<List<UserList>>
}
Activity(为简洁起见缩短):
class UserListsActivity: Activity() {
val viewModel: UserListsViewModel
val adapter: UserListAdapter
fun onCreate(savedInstanceState: Bundle?) {
viewModel.allLists.observe(this, Observer { newData ->
adapter.setData(newData)
adapter.notifyDataSetChanged()
)
}
}
视图模型(为简洁起见缩短)
class UserListsViewModel(val userId: Int, val repository: ListsRepository ): ViewModel() {
val allLists = MediatorLiveData<List<UserList>>()
val isSorting = false
init {
allLists.addSource(repository.getAllUserLists(userId)) { userLists ->
val result = userLists
if(isSorting) {
result = result.sortedBy { it.name }
}
value = result
}
}
}
现在让我们进入实际问题:
只要打开排序功能,一切都很好,用户将看到“列表 1”、“列表 2”、“列表 3”、“列表 4”。
但是现在,假设用户关闭了排序功能,并添加了另一个列表“list 0”
现在发生的事情是:
MediatorLiveData
从数据库接收更新为“列表 3”、“列表 1”、“列表 2”、“列表 4”、“列表 0”(数据库的原始顺序,加上新列表)
isSorting
标志关闭,因此它按原样将值设置为 returned 数据
- 显示给用户的数据未排序,但之前的排序现在丢失了
所需结果:“列表 1”、“列表 2”、“列表 3”、“列表 4”、“列表 0”(未排序,但保留之前的顺序)
实际结果:“列表 3”、“列表 1”、“列表 2”、“列表 4”、“列表 0”(列表 1、2、3 的先前排序现已丢失)。
我希望我说清楚了。这有可能在不增加太多复杂性的情况下实现吗?在 android 应用程序中提供排序功能的“标准方式”是什么?
谢谢
我觉得你的假设有问题
Now lets get to the actual issue:
As long as the sorting feature is turned on, everything is fine and the user will see "list 1", "list 2", "list 3", "list 4".
But now, lets say the user turns the sorting feature off, and adds another list, "list 0"
[...]
Desired result: "list 1","list 2","list 3","list 4","list 0" (unsorted, but previous order is kept)
Actual result: "list 3","list 1","list 2","list 4","list 0" (the previous sorting of lists 1,2,3 is now lost).
作为用户,我希望在打开或关闭排序后立即更改排序行为。因此,在用户关闭排序后,项目的显示顺序将再次为:"list 3","list 1","list 2","list 4"。然后添加另一个列表时,它将(不排序)附加到末尾,就像您写的那样,但不会对用户进行令人惊讶的排序更改。
如果您真的想实现上述目标,我想您必须在 table 中添加一个 sort_index
字段,用作排序标准。然后,您可以在按列表标题、创建日期等排序时更新该字段。然后,当您按此字段排序时(您也可以让 room/the 数据库为您排序,这应该更有效),用户将获得一致的应用程序行为。
更高级的方法可能是创建一个额外的“映射”table,其中有一列用于列表索引,另一列用于实际订单(sort_index
来自上面)。当然,此列表也可以保存在您的视图模型中,而不是数据库中的 field/table,因为它可以创建 on-the-fly.
拥有此字段的另一个好处是它允许您的用户通过拖放手动对列表进行排序,您只需相应地更新索引。
这是我 运行 遇到的另一个问题,以及我决定采用的解决方案(希望这对其他人有帮助):
首先,我采用了建议的方法,即在用户单击“排序”按钮后立即更改排序顺序。意思不是从“现在排序,现在不排序”的角度来考虑它,我现在将其视为“数据总是按某些东西排序(即使它 'unsorted')”。
我希望“未排序”列表(默认排序)与项目出现在数据库中的顺序相同,但是我的 ViewModel
的设置方式使我无法直接获取原始数据库项目。
为了简洁起见,我有这样的东西:
//inside view model class
val allLists : LiveData<List<UserList>> = repository.getAllLists()
所以当我想使用默认排序时,我想到了 2 个选项:
- 在存储库中添加一个函数,将
getAllLists()
的值设置为其自身,从而触发观察者 - 但这似乎不是一个好的解决方案
- 在
ViewModel
中维护另一个列表,它是数据库返回的列表的副本,因此它总是按照数据库检索它的方式排序(并且“默认排序”的值为该列表的)-但这会浪费内存和额外的复杂性(不多,但仍然)
我最后做的是在我的项目中添加一个字段 created
,只要创建我的项目的实例并将其用作我的“默认排序”,它就会简单地设置为 System.currentTimeMillis()
-添加的代码非常少(甚至不到半行),并且完全没有增加复杂性。
我正在为 android 编写一个简单的待办事项列表样式应用程序,但我在添加排序功能时遇到了问题。
我有一个屏幕 RecyclerView
显示属于用户的所有列表,我想添加一个允许用户打开或关闭排序的功能。问题是我似乎无法找到一种方法来使显示给用户的列表在进行另一次更改后保持一致排序。
假设列表保存在 Room
数据库中。
实现我想要的最简单和最一致的方法是对数据库本身中的数据进行排序,但这可能不是可行的方法,因为它实现起来会很复杂(如果可能的话)。
很可能我应该在我的视图模型中对数据进行本地排序。我想我可以使用 MediatorLiveData
来做到这一点,它将观察数据库,而主机 activity 本身也会观察到它。在更新 MediatorLiveData
之前,我会使用标志 isSorting
对列表进行排序,以按原样 return 数据或在交付结果之前对其进行排序。
我的问题的关键是我希望能够打开和关闭此功能,同时在之后(执行另一项操作时)仍然保持数据的顺序。这是典型事件流的示例:
- 用户打开应用程序和数据库returns:“列表 3”、“列表 1”、“列表 2”。
- 数据按原样显示给用户(未排序)
- 用户开启排序功能
- 视图模型在本地对支持列表进行排序
- activity中的观察者收到排序后的列表
- activity 现在显示“列表 1”、“列表 2”、“列表 3”
- 用户添加了一个新列表:“列表 4”。
在进入问题部分之前,这里是一些代码:
存储库(为简洁起见缩短):
class ListsRepository {
val db = Database().getInstance();
fun getAllUserLists(userId: Int): LiveData<List<UserList>>
}
Activity(为简洁起见缩短):
class UserListsActivity: Activity() {
val viewModel: UserListsViewModel
val adapter: UserListAdapter
fun onCreate(savedInstanceState: Bundle?) {
viewModel.allLists.observe(this, Observer { newData ->
adapter.setData(newData)
adapter.notifyDataSetChanged()
)
}
}
视图模型(为简洁起见缩短)
class UserListsViewModel(val userId: Int, val repository: ListsRepository ): ViewModel() {
val allLists = MediatorLiveData<List<UserList>>()
val isSorting = false
init {
allLists.addSource(repository.getAllUserLists(userId)) { userLists ->
val result = userLists
if(isSorting) {
result = result.sortedBy { it.name }
}
value = result
}
}
}
现在让我们进入实际问题:
只要打开排序功能,一切都很好,用户将看到“列表 1”、“列表 2”、“列表 3”、“列表 4”。
但是现在,假设用户关闭了排序功能,并添加了另一个列表“list 0”
现在发生的事情是:
MediatorLiveData
从数据库接收更新为“列表 3”、“列表 1”、“列表 2”、“列表 4”、“列表 0”(数据库的原始顺序,加上新列表)isSorting
标志关闭,因此它按原样将值设置为 returned 数据- 显示给用户的数据未排序,但之前的排序现在丢失了
所需结果:“列表 1”、“列表 2”、“列表 3”、“列表 4”、“列表 0”(未排序,但保留之前的顺序)
实际结果:“列表 3”、“列表 1”、“列表 2”、“列表 4”、“列表 0”(列表 1、2、3 的先前排序现已丢失)。
我希望我说清楚了。这有可能在不增加太多复杂性的情况下实现吗?在 android 应用程序中提供排序功能的“标准方式”是什么?
谢谢
我觉得你的假设有问题
Now lets get to the actual issue:
As long as the sorting feature is turned on, everything is fine and the user will see "list 1", "list 2", "list 3", "list 4".
But now, lets say the user turns the sorting feature off, and adds another list, "list 0"
[...]
Desired result: "list 1","list 2","list 3","list 4","list 0" (unsorted, but previous order is kept)
Actual result: "list 3","list 1","list 2","list 4","list 0" (the previous sorting of lists 1,2,3 is now lost).
作为用户,我希望在打开或关闭排序后立即更改排序行为。因此,在用户关闭排序后,项目的显示顺序将再次为:"list 3","list 1","list 2","list 4"。然后添加另一个列表时,它将(不排序)附加到末尾,就像您写的那样,但不会对用户进行令人惊讶的排序更改。
如果您真的想实现上述目标,我想您必须在 table 中添加一个 sort_index
字段,用作排序标准。然后,您可以在按列表标题、创建日期等排序时更新该字段。然后,当您按此字段排序时(您也可以让 room/the 数据库为您排序,这应该更有效),用户将获得一致的应用程序行为。
更高级的方法可能是创建一个额外的“映射”table,其中有一列用于列表索引,另一列用于实际订单(sort_index
来自上面)。当然,此列表也可以保存在您的视图模型中,而不是数据库中的 field/table,因为它可以创建 on-the-fly.
拥有此字段的另一个好处是它允许您的用户通过拖放手动对列表进行排序,您只需相应地更新索引。
这是我 运行 遇到的另一个问题,以及我决定采用的解决方案(希望这对其他人有帮助):
首先,我采用了建议的方法,即在用户单击“排序”按钮后立即更改排序顺序。意思不是从“现在排序,现在不排序”的角度来考虑它,我现在将其视为“数据总是按某些东西排序(即使它 'unsorted')”。
我希望“未排序”列表(默认排序)与项目出现在数据库中的顺序相同,但是我的 ViewModel
的设置方式使我无法直接获取原始数据库项目。
为了简洁起见,我有这样的东西:
//inside view model class
val allLists : LiveData<List<UserList>> = repository.getAllLists()
所以当我想使用默认排序时,我想到了 2 个选项:
- 在存储库中添加一个函数,将
getAllLists()
的值设置为其自身,从而触发观察者 - 但这似乎不是一个好的解决方案 - 在
ViewModel
中维护另一个列表,它是数据库返回的列表的副本,因此它总是按照数据库检索它的方式排序(并且“默认排序”的值为该列表的)-但这会浪费内存和额外的复杂性(不多,但仍然)
我最后做的是在我的项目中添加一个字段 created
,只要创建我的项目的实例并将其用作我的“默认排序”,它就会简单地设置为 System.currentTimeMillis()
-添加的代码非常少(甚至不到半行),并且完全没有增加复杂性。