【问题标题】:Android PagingLibrary network + databaseAndroid PagingLibrary 网络+数据库
【发布时间】:2020-06-29 07:01:18
【问题描述】:

我正在开发消息应用程序,我正在实现数据库 + 网络以保存来自 api 的聊天消息并从数据库中显示它们。当数据库没有更多数据时,我使用BoundaryCallback 获取消息。我的 api 是这样工作的:

getlist(       @Query("msgid") long msgid,
               @Query("loadolder") boolean olderOrNewer,
               @Query("showcurrentMessage") boolean showcurrentMessage,
               @Query("MsgCountToLoad") int MsgCountToLoad);
  • msgid:该聊天的最后一条消息 id。如果 db 为空,我请求 如果数据库有数据但没有数据,则使用chat.getlastmessageid 更多数据我将在 db 中发送最后一条消息 id 以加载更多,如果首先 打开聊天时,db 中的最后一条消息 id 不等于 chat.lastmessageid 表示有新消息要加载。
  • loadolder : 这个标志 false 告诉 api 从中加载新消息 我发送给你的消息 id 并且如果标志设置为 true 表示加载 我发送给您的来自此消息 ID 的旧消息
  • showcurrentMessage:如果为真,它也会给我当前消息 (msgid)
  • MsgCountToLoad : 从 api 获取多少条消息

问题是如何处理Pagginglibrary 中的这些东西?如何告诉它加载基于滚动位置的旧消息或新消息。第一次加载数据很容易,它会返回 null 对象,所以我将在下次打开聊天时使用 chat.lastmessageid,我可以检查 chat.lastmessageid 是否等于 db.lastmessageid 并告诉它加载更多新消息。

【问题讨论】:

标签: android paging android-architecture-components android-paging


【解决方案1】:

PagedList.BoundaryCallback 有两个单独的 API 用于前置和附加。

您应该考虑实现这些方法:

onItemAtEndLoaded
onItemAtFrontLoaded

假设您的初始加载加载的是最新消息,而向上滚动加载的是较旧的消息,您只需将 true 传递给 onItemAtFrontLoaded 中的 loadolderfalse 传递 onItemAtEndLoaded 中的 false

【讨论】:

    【解决方案2】:

    我正在处理我的最后一个消息应用程序项目。我们在项目中做的最重要和最常见的事情之一是逐渐从网络或数据库中加载数据,这可能是因为无法一次加载大量实体​​。

    如果您不熟悉分页库或实时数据概念,请先花时间研究它们,因为我不打算在这里讨论它们。您可以使用很多资源来学习它们。

    我的解决方案包括两个主要部分!

    1. 使用分页库观察数据库。
    2. 观察 RecyclerView 以了解何时向服务器请求数据页。

    为了演示,我们将使用一个表示 Person 的实体类:

    @Entity(tableName = "persons")
    data class Person(
    @ColumnInfo(name = "id") @PrimaryKey val id: Long,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "update_time") val updateTime: Long
    
    )
    

    1.观察数据库

    让我们从第一个更简单的开始: 为了观察数据库,我们将在 dao 中定义一个返回 DataSource.Factory

    的方法
    @Dao
    interface PersonDao {
    @Query("SELECT * FROM persons ORDER BY update_time DESC")
    fun selectPaged(): DataSource.Factory<Int, Person>
    }
    

    现在在我们的 ViewModel 中,我们将从我们的工厂构建一个 PagedList

    class PersonsViewModel(private val dao: PersonDao) : ViewModel() {
    val pagedListLiveData : LiveData<PagedList<Person>> by lazy {
        val dataSourceFactory = personDao.selectPaged()
        val config = PagedList.Config.Builder()
                .setPageSize(PAGE_SIZE)
                .build()
        LivePagedListBuilder(dataSourceFactory, config).build()
    }
    }
    

    从我们的角度来看,我们可以观察到分页列表

    class PersonsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_persons)
    
        viewModel.pagedListLiveData.observe(this, Observer{
            pagedListAdapter.submitList(it)
        })
      }
    }
    

    好的,现在这基本上是我们第一部分应该做的。请注意,我们使用的是 PagedListAdapter。此外,我们可以对 PagedList.Config 对象进行更多自定义,但为简单起见,我们将其省略。再次请注意,我们没有在 LivePagedListBuilder 上使用 BoundaryCallback。

    2。观察 RecyclerView

    这里基本上我们应该做的是观察列表,根据我们现在在列表中的位置,请求服务器为我们提供相应的数据页面。为了观察 RecyclerView 的位置,我们将使用一个名为 Paginate 的简单库。

    class PersonsActivity : AppCompatActivity(), Paginate.Callbacks {
    private var page = 0
    private var isLoading = false
    private var hasLoadedAllItems = false
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_persons)
    
        viewModel.pagedListLiveData.observe(this, Observer{
            pagedListAdapter.submitList(it)
        })
        Paginate.with(recyclerView, this).build()
     }
    override fun onLoadMore() {
        // send the request to get corresponding page
     }
    override fun isLoading(): Boolean = isLoading
    override fun hasLoadedAllItems(): Boolean = hasLoadedAllItems
    }
    

    如您所见,我们将 Paginate 与回收器视图绑定,现在我们有三个回调。 isLoading() 应该返回网络的状态。 hasLoadedAllItems() 显示我们是否已经到达最后一页并且没有更多数据可以从服务器加载。我们所做的大部分工作是实现最后一个方法 onLoadMore()。

    在这个阶段我们应该做三件事:

    1. 根据 recyclerView 的位置,我们要求服务器向我们展示正确的数据页面。
    2. 我们使用来自服务器的新数据更新数据库,从而更新 PagedList 并显示新数据。别忘了我们正在观察数据库!
    3. 如果请求失败,我们会显示错误。

    通过这些简单的步骤,我们解决了两个问题。 首先,尽管 BoundaryCallbak 没有回调来获取已获取的数据,但我们正在按需请求每个页面,以便我们可以注意到更新的实体并更新我们自己的本地数据库。 其次,我们可以很容易地显示网络的状态,也可以显示可能的网络故障。 听起来不错吧?好吧,我们还没有解决一个特定的问题。这就是如果一个实体从远程服务器中删除的情况。我们怎么会注意到这一点!这就是数据排序的用武之地。通过一个非常古老的数据排序技巧,我们可以注意到我们之间的差距。例如,如果服务器返回的 JSON 页面如下所示,我们现在可以根据他们的 update_time 对我们的人员进行排序:

    {
     "persons": [{
     "id": 1,
     "name": "Reza",
     "update_time": 1535533985000
    }, {
     "id": 2,
     "name": "Nick",
     "update_time": 1535533985111
    }, {
     "id": 3,
     "name": "Bob",
     "update_time": 1535533985222
    }, {
     "id": 4,
     "name": "Jafar",
     "update_time": 1535533985333
    }, {
     "id": 5,
     "name": "Feryal",
     "update_time": 1535533985444
    }],
     "page": 0,
     "limit": 5,
     "hasLoadedAllItems": false
    }
    

    现在我们可以确定,如果我们本地数据库中有一个人,其update_time在这个列表的第一个和最后一个人之间,但不在这些人之间,实际上是从远程服务器中删除的因此我们也应该删除它。 我希望我太模糊了,但请看下面的代码

    override fun onLoadMore() {
    if (!isLoading) {
        isLoading = true
        viewModel.loadPersons(page++).observe(this, Observer { response ->
            isLoading = false
            if (response.isSuccessful()) {
                hasLoadedAllItems = response.data.hasLoadedAllItems
            } else {
                showError(response.errorBody())
            }
        })
      }
    }
    

    但神奇的是发生在 ViewModel 中

    class PersonsViewModel(
        private val dao: PersonDao,
        private val networkHelper: NetworkHelper
     ) : ViewModel() {
     fun loadPersons(page: Int): LiveData<Response<Pagination<Person>>> {
        val response = 
                MutableLiveData<Response<Pagination<Person>>>()
        networkHelper.loadPersons(page) {
            dao.updatePersons(
                    it.data.persons,
                    page == 0,
                    it.hasLoadedAllItems)
            response.postValue(it)
        }
        return response
       }
     }
    

    如您所见,我们发出网络结果并更新我们的数据库

    @Dao
    interface PersonDao {
    @Transaction
    fun updatePersons(
            persons: List<Person>,
            isFirstPage: Boolean,
            hasLoadedAllItems: Boolean) {
        val minUpdateTime = if (hasLoadedAllItems) {
            0
        } else {
            persons.last().updateTime
        }
    
        val maxUpdateTime = if (isFirstPage) {
            Long.MAX_VALUE
        } else {
            persons.first().updateTime
        }
    
        deleteRange(minUpdateTime, maxUpdateTime)
        insert(persons)
       }
    
       @Query("DELETE FROM persons WHERE
            update_time BETWEEN
            :minUpdateTime AND :maxUpdateTime")
       fun deleteRange(minUpdateTime: Long, maxUpdateTime: Long)
       @Insert(onConflict = REPLACE)
       fun insert(persons: List<Person>)
      }
    

    在我们的 dao 中,我们首先删除从服务器返回的列表中的第一个和最后一个人之间的所有人员,然后将列表插入到数据库中。这样,我们确保在服务器上删除的任何人也会在我们的本地数据库中删除。另请注意,我们在数据库@Transaction 中敲击这两个方法调用以进行更好的优化。数据库的更改将通过我们的 PagedList 发出,从而更新 ui,我们就完成了。

    【讨论】:

    • 感谢您对我基于 BoundaryCallback 设计的详细解释,但我很确定您的做法会很好。这将帮助某人
    • 想象一下为你的后端模型添加一个更新时间变量,只是为了让 ui 的东西在前端工作
    猜你喜欢
    • 1970-01-01
    • 2022-07-21
    • 1970-01-01
    • 1970-01-01
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    • 2011-04-10
    • 1970-01-01
    相关资源
    最近更新 更多