在Realm中使用分页库

在Android部门的一次会议上,我听到了我们的一位开发人员如何制作一个小的库,该库有助于在使用Realm时创建“无尽”列表,同时保持“延迟加载”和通知。

我撰写并撰写了一篇文章草稿,该文章草稿几乎没有变化,我正在与您分享。 他承诺,如果出现问题,他会忙于完成任务并发表评论。

无休止的清单和交钥匙解决方案


我们面临的任务之一是在列表中显示信息,在滚动浏览时,数据将被加载并以不可见的方式插入用户。 对于用户而言,他似乎将滚动一个无尽列表。

该算法大致如下:

  • 我们从第一页的缓存中获取数据;
  • 如果缓存为空-我们获取服务器数据,则将其显示在列表中并写入数据库;
  • 如果有缓存,则将其加载到列表中;
  • 如果到达数据库的末尾,则我们从服务器请求数据,将其显示在列表中并写入数据库。

简化:要显示列表,首先轮询缓存,然后加载新数据的信号在缓存末尾。

要实现无限滚动,可以使用现成的解决方案:


我们将Realm用作移动数据库,并且尝试了上述所有方法之后,我们停止使用Paging库。

乍一看,Android Paging Library是下载数据的出色解决方案,将sqlite与Room结合使用时,它作为数据库非常出色。 但是,将Realm用作数据库时,我们会失去所有惯用的东西-延迟加载和数据更改通知 。 我们不想放弃所有这些事情,但是同时使用Paging库。

也许我们不是第一个需要它的人


快速搜索立即产生了一个解决方案-Realm君主制图书馆。 经过快速研究,结果表明该解决方案不适合我们-库不支持延迟加载或通知。 我必须创建自己的。

因此,要求是:

  1. 继续使用Realm;
  2. 为Realm保存延迟加载;
  3. 保存通知;
  4. 就像分页库建议的那样,使用分页库从数据库中加载数据并从服务器分页数据。

从一开始,让我们尝试弄清楚Paging库是如何工作的,以及如何使我们感觉良好。

简要地说-该库包含以下组件:

DataSource-用于逐页加载数据的基类。
它具有以下实现:PageKeyedDataSource,PositionalDataSource和ItemKeyedDataSource,但是它们的目的对我们而言现在并不重要。

PagedList-一个从DataSource加载数据块的列表。 但是,由于我们使用Realm,因此批量加载数据与我们无关。
PagedListAdapter-负责显示由PagedList加载的数据的类。

在参考实现的源代码中,我们将看到电路的工作原理。

1. getItem(int索引)方法中的PagedListAdapter调用PagedList的loadAround(int索引)方法:

/** * Get the item from the current PagedList at the specified index. * <p> * Note that this operates on both loaded items and null padding within the PagedList. * * @param index Index of item to get, must be >= 0, and < {@link #getItemCount()}. * @return The item, or null, if a null placeholder is at the specified position. */ @SuppressWarnings("WeakerAccess") @Nullable public T getItem(int index) { if (mPagedList == null) { if (mSnapshot == null) { throw new IndexOutOfBoundsException( "Item count is zero, getItem() call is invalid"); } else { return mSnapshot.get(index); } } mPagedList.loadAround(index); return mPagedList.get(index); } 

2. PagedList执行检查并调用void方法tryDispatchBoundaryCallbacks(布尔值发布):

 /** * Load adjacent items to passed index. * * @param index Index at which to load. */ public void loadAround(int index) { if (index < 0 || index >= size()) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size()); } mLastLoad = index + getPositionOffset(); loadAroundInternal(index); mLowestIndexAccessed = Math.min(mLowestIndexAccessed, index); mHighestIndexAccessed = Math.max(mHighestIndexAccessed, index); /* * mLowestIndexAccessed / mHighestIndexAccessed have been updated, so check if we need to * dispatch boundary callbacks. Boundary callbacks are deferred until last items are loaded, * and accesses happen near the boundaries. * * Note: we post here, since RecyclerView may want to add items in response, and this * call occurs in PagedListAdapter bind. */ tryDispatchBoundaryCallbacks(true); } 

3.用这种方法,检查是否需要下载以下部分数据,并出现下载请求:

 /** * Call this when mLowest/HighestIndexAccessed are changed, or * mBoundaryCallbackBegin/EndDeferred is set. */ @SuppressWarnings("WeakerAccess") /* synthetic access */ void tryDispatchBoundaryCallbacks(boolean post) { final boolean dispatchBegin = mBoundaryCallbackBeginDeferred && mLowestIndexAccessed <= mConfig.prefetchDistance; final boolean dispatchEnd = mBoundaryCallbackEndDeferred && mHighestIndexAccessed >= size() - 1 - mConfig.prefetchDistance; if (!dispatchBegin && !dispatchEnd) { return; } if (dispatchBegin) { mBoundaryCallbackBeginDeferred = false; } if (dispatchEnd) { mBoundaryCallbackEndDeferred = false; } if (post) { mMainThreadExecutor.execute(new Runnable() { @Override public void run() { dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); } }); } else { dispatchBoundaryCallbacks(dispatchBegin, dispatchEnd); } } 

4.结果,所有调用都落入数据源,从数据库或其他来源下载数据:

 @SuppressWarnings("WeakerAccess") /* synthetic access */ void dispatchBoundaryCallbacks(boolean begin, boolean end) { // safe to deref mBoundaryCallback here, since we only defer if mBoundaryCallback present if (begin) { //noinspection ConstantConditions mBoundaryCallback.onItemAtFrontLoaded(mStorage.getFirstLoadedItem()); } if (end) { //noinspection ConstantConditions mBoundaryCallback.onItemAtEndLoaded(mStorage.getLastLoadedItem()); } } 

虽然一切看起来都很简单-只需接受并执行。 正当事务:

  1. 创建您自己的PagedList(RealmPagedList)实现,它将与RealmModel一起使用;
  2. 创建您自己的PagedStorage(RealmPagedStorage)实现,它将与OrderedRealmCollection一起使用;
  3. 创建自己的DataSource实现(RealmDataSource),该实现将与RealmModel一起使用;
  4. 创建自己的适配器以使用RealmList;
  5. 删除不必要的,添加必要的;
  6. 做完了

我们忽略了次要的技术细节,这里是结果-RealmPagination库 。 让我们尝试创建一个显示用户列表的应用程序。

0.将库添加到项目中:

 allprojects { repositories { maven { url "https://jitpack.io" } } } implementation 'com.github.magora-android:realmpagination:1.0.0' 


1.创建用户类:

 @Serializable @RealmClass open class User : RealmModel { @PrimaryKey @SerialName("id") var id: Int = 0 @SerialName("login") var login: String? = null @SerialName("avatar_url") var avatarUrl: String? = null @SerialName("url") var url: String? = null @SerialName("html_url") var htmlUrl: String? = null @SerialName("repos_url") var reposUrl: String? = null } 

2.创建一个数据源:

 class UsersListDataSourceFactory( private val getUsersUseCase: GetUserListUseCase, private val localStorage: UserDataStorage ) : RealmDataSource.Factory<Int, User>() { override fun create(): RealmDataSource<Int, User> { val result = object : RealmPageKeyedDataSource<Int, User>() { override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, User>) {...} override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, User>) { ... } override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, User>) { ... } } return result } override fun destroy() { } } 

3.创建一个适配器:

 class AdapterUserList( data: RealmPagedList<*, User>, private val onClick: (Int, Int) -> Unit ) : BaseRealmListenableAdapter<User, RecyclerView.ViewHolder>(data) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_user, parent, false) return UserViewHolder(view) } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { ... } } 

4.创建一个ViewModel:

 private const val INITIAL_PAGE_SIZE = 50 private const val PAGE_SIZE = 30 private const val PREFETCH_DISTANCE = 10 class VmUsersList( app: Application, private val dsFactory: UsersListDataSourceFactory, ) : AndroidViewModel(app), KoinComponent { val contentData: RealmPagedList<Int, User> get() { val config = RealmPagedList.Config.Builder() .setInitialLoadSizeHint(INITIAL_PAGE_SIZE) .setPageSize(PAGE_SIZE) .setPrefetchDistance(PREFETCH_DISTANCE) .build() return RealmPagedListBuilder(dsFactory, config) .setInitialLoadKey(0) .setRealmData(localStorage.getUsers().users) .build() } fun refreshData() { ... } fun retryAfterPaginationError() { ... } override fun onCleared() { super.onCleared() dsFactory.destroy() } } 

5.初始化列表:

 recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.adapter = AdapterUserList(viewModel.contentData) { user, position -> //... } 

6.创建一个带有列表的片段:

 class FragmentUserList : BaseFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView.layoutManager = LinearLayoutManager(context) recyclerView.adapter = AdapterUserList(viewModel.contentData) { user, position -> ... } } 

7.完成。

事实证明,使用Realm就像Room一样简单。 Sergey发布了该库源代码和一个使用示例 。 如果遇到类似情况,您将不必再砍掉另一辆自行车。

Source: https://habr.com/ru/post/zh-CN467097/


All Articles