【问题标题】:how to properly sort a list using repository pattern and live data in an MVVM android application?如何在 MVVM android 应用程序中使用存储库模式和实时数据正确排序列表?
【发布时间】:2020-11-02 01:59:45
【问题描述】:

我正在为 Android 编写一个简单的待办事项列表样式应用程序,但在添加排序功能时遇到了问题。

我有一个带有RecyclerView 的屏幕,显示属于该用户的所有列表,我想添加一个允许用户打开或关闭排序的功能。问题是我似乎无法找到一种方法让向用户显示的列表在进行另一次更改后保持一致的排序。

假设列表保存在Room 数据库中。 实现我想要的最简单和最一致的方法是对数据库本身的数据进行排序,但这可能不是要走的路,因为实现起来会很复杂(如果可能的话)。

我很可能应该在我的视图模型中对数据进行本地排序。我想我可以使用MediatorLiveData 来做到这一点,它会观察数据库,而主机活动本身会观察到它。在更新 MediatorLiveData 之前,我会使用标志 isSorting 对列表进行排序,以便按原样返回数据或在传递结果之前对其进行排序。

我的问题的关键是我希望能够打开和关闭此功能,同时仍保持之后的数据顺序(执行另一个操作时)。以下是典型事件流的示例:

  1. 用户打开应用,数据库返回:“list 3”、“list 1”、“list 2”。
  2. 数据按原样(未排序)显示给用户
  3. 用户开启排序功能
  4. 视图模型在本地对后备列表进行排序
  5. 活动中的观察者收到排序列表
  6. 活动现在显示“列表 1”、“列表 2”、“列表 3”
  7. 用户添加了一个新列表:“list 4”。

在我进入问题部分之前,这里有一些代码:

存储库(为简洁而缩写):

class ListsRepository {
    val db = Database().getInstance();
    
    fun getAllUserLists(userId: Int): LiveData<List<UserList>>
}

活动(为简洁起见):

class UserListsActivity: Activity() {

    val viewModel: UserListsViewModel    
    val adapter: UserListAdapter

    fun onCreate(savedInstanceState: Bundle?) {
        viewModel.allLists.observe(this, Observer { newData ->
            adapter.setData(newData)
            adapter.notifyDataSetChanged()
        )
    }
}

Viewmodel(为简洁起见)

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”。

但是现在,假设用户关闭了排序功能,并添加了另一个列表“列表 0”

现在发生的是:

  1. MediatorLiveData 从数据库接收更新为“列表 3”、“列表 1”、“列表 2”、“列表 4”、“列表 0”(数据库的原始顺序,加上新列表)
  2. isSorting 标志已关闭,因此它按原样将值设置为返回的数据
  3. 向用户显示的数据未排序,但之前的排序现在丢失了

期望的结果:"list 1","list 2","list 3","list 4","list 0"(未排序,但保留之前的顺序)

实际结果:"list 3","list 1","list 2","list 4","list 0"(之前对列表 1,2,3 的排序现在丢失了)。

我希望我说清楚了。这是否可以在不增加太多复杂性的情况下实现?在 Android 应用中提供排序功能的“标准方式”是什么?

谢谢

【问题讨论】:

  • 你是说数据库响应的顺序是实现我想要的关键吗?因为我只以Room 为例,但这个问题的含义更广泛。而且,除了插入之外,还有其他操作,比如重命名。
  • 这里有一条评论建议我应该始终排序,但只在 DB 的大小减去 1 之前。该评论现在被删除,但我想发表评论以防其他人想到这一点。这种方法可能适用于添加项目,但不适用于删除。想象一下 DB 持有 3,4,1,2。用户打开排序并获得 1,2,3,4。现在用户关闭排序并删除列表 4。数据库现在保存 3,1,2,如果视图模型只排序到最后一个条目,用户将获得 1,3,2 我希望他看到的位置1,2,3

标签: android sorting mvvm viewmodel repository-pattern


【解决方案1】:

我认为你的假设有问题

现在让我们进入实际问题:

只要打开排序功能,一切正常,用户将看到“列表 1”、“列表 2”、“列表 3”、“列表 4”。

但是现在,假设用户关闭了排序功能,并添加了另一个列表“列表 0”

[...]

期望的结果:"list 1","list 2","list 3","list 4","list 0"(未排序,但保留之前的顺序)

实际结果:"list 3","list 1","list 2","list 4","list 0"(之前对列表 1,2,3 的排序现在丢失了)。

作为用户,我希望在打开或关闭排序后立即更改排序行为。所以用户关闭排序后,项目的显示顺序又会是:"list 3","list 1","list 2","list 4"。当添加另一个列表时,它会(不排序)附加到末尾,就像你写的那样,但不会给用户带来意外的排序变化。

如果你真的想实现上面描述的,我想你必须在你的表中添加一个sort_index 字段作为排序标准。然后,您可以在按列表标题、创建日期等排序时更新该字段。然后,当您按此字段排序时(您也可以让空间/数据库为您做,这应该更有效),用户会获得应用程序的一致行为。

更高级的可能是创建一个额外的“映射”表,其中有一列用于列表索引,另一列用于实际订单(上面的sort_index)。当然,这个列表也可以保存在您的视图模型中,而不是数据库中的字段/表,因为它可以即时创建。

拥有此字段的另一个好处是,它允许您的用户通过拖放手动对列表进行排序,您只需相应地更新索引。

【讨论】:

  • change the sorting behavior as soon as I turned sorting off - 也想过这个问题。不完全是我想要的,但可能已经足够好了:没有增加复杂性,用户不会像你说的那样“感到惊讶”。 I guess you will have to add a sort_index - 也想过这个问题。不过,它很快在我的脑海中变得太复杂了(但我可能是错的)。 Another benefit of having this field is that it would allow your user to manually sort the list via drag and drop - 也想过这个问题。虽然我想拥有它,但由于增加了复杂性,我可能不得不放弃它
【解决方案2】:

这是我遇到的另一个问题,我决定采用的解决方案(希望这对其他人有帮助):

首先,一旦用户单击“排序”按钮,我就采用了更改排序顺序的建议方法。意思不是用“现在它的排序,现在它不是”来考虑它,我现在认为它是“数据总是按某些东西排序(即使它'未排序')”。 我希望“未排序”列表(默认排序)与数据库中出现的项目的顺序相同,但是我的ViewModel 的设置方式使我无法直接获取原始数据库项目。

为了简洁起见,我有这样的事情:

//inside view model class
val allLists : LiveData<List<UserList>> = repository.getAllLists()

所以当我想使用默认排序时,我想到了 2 个选项:

  1. 在存储库中添加一个函数,将 getAllLists() 的值设置为自身,从而触发观察者 - 但这似乎不是一个好的解决方案
  2. ViewModel 中维护另一个列表,它是数据库返回的列表的副本,因此它将始终按照数据库检索它的方式进行排序(并且“默认排序”将具有该列表的值) - 但这是对内存的浪费和额外的复杂性(不多,但仍然如此)

我最终做的是在我的项目中添加一个字段created,只要创建我的项目的实例并将其用作我的“默认排序”,它就会简单地设置为System.currentTimeMillis() - 非常少的代码添加(不是甚至半行),并没有增加任何复杂性。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-26
    • 1970-01-01
    • 2020-12-26
    • 1970-01-01
    • 2010-12-02
    相关资源
    最近更新 更多