Menggunakan Paging library dengan Realm

Pada salah satu pertemuan departemen Android, saya mendengar bagaimana salah satu pengembang kami membuat lib kecil yang membantu membuat daftar "tak berujung" saat menggunakan Realm, menjaga "pemuatan malas" dan pemberitahuan.

Saya membuat dan menulis draft artikel, yang hampir tidak berubah, saya bagikan dengan Anda. Sementara itu, ia berjanji akan mengerjakan tugas dan memberikan komentar jika muncul pertanyaan.

Solusi daftar dan turnkey tak berujung


Salah satu tugas yang kita hadapi adalah untuk menampilkan informasi dalam daftar, ketika menggulir di mana, data dimuat dan dimasukkan tanpa terlihat ke pengguna. Untuk pengguna, sepertinya dia akan menggulir daftar yang tak ada habisnya.

Algoritma ini kira-kira sebagai berikut:

  • kami mendapatkan data dari cache untuk halaman pertama;
  • jika cache kosong - kami mendapatkan data server, menampilkannya dalam daftar dan menulis ke database;
  • jika ada cache, muat ke dalam daftar;
  • jika kita mencapai akhir database, maka kita meminta data dari server, menampilkannya dalam daftar dan menulis ke database.

Sederhana: untuk menampilkan daftar, cache pertama kali disurvei, dan sinyal untuk memuat data baru adalah akhir dari cache.

Untuk menerapkan pengguliran tanpa akhir, Anda dapat menggunakan solusi yang sudah jadi:


Kami menggunakan Realm sebagai basis data seluler, dan setelah mencoba semua pendekatan di atas, kami berhenti menggunakan pustaka Paging.

Sepintas, Android Paging Library adalah solusi yang sangat baik untuk mengunduh data, dan ketika menggunakan sqlite bersamaan dengan Room, itu sangat baik sebagai basis data. Namun, saat menggunakan Realm sebagai basis data, kami kehilangan semua yang biasa kami gunakan - malas memuat dan mengubah pemberitahuan data . Kami tidak ingin menyerahkan semua hal ini, tetapi pada saat yang sama menggunakan perpustakaan Paging.

Mungkin kita bukan yang pertama membutuhkannya


Pencarian cepat segera menghasilkan solusi - perpustakaan kerajaan Realm . Setelah studi cepat, ternyata solusi ini tidak cocok untuk kami - perpustakaan tidak mendukung pemuatan atau pemberitahuan malas. Saya harus membuat sendiri.

Jadi, persyaratannya adalah:

  1. Terus menggunakan Realm;
  2. Simpan pemuatan malas untuk Realm;
  3. Simpan pemberitahuan;
  4. Gunakan Paging library untuk memuat data dari database dan paginate data dari server, seperti yang disarankan Paging library.

Dari awal, mari kita coba mencari tahu bagaimana perpustakaan Paging bekerja, dan apa yang harus dilakukan untuk membuat kita merasa baik.

Secara singkat - perpustakaan terdiri dari komponen-komponen berikut:

DataSource - kelas dasar untuk memuat data halaman demi halaman.
Ini memiliki implementasi: PageKeyedDataSource, PositionalDataSource dan ItemKeyedDataSource, tetapi tujuan mereka tidak penting bagi kami sekarang.

PagedList - daftar yang memuat data dalam potongan-potongan dari DataSource. Tapi karena kami menggunakan Realm, memuat data dalam batch tidak relevan bagi kami.
PagedListAdapter - kelas yang bertanggung jawab untuk menampilkan data yang dimuat oleh PagedList.

Dalam kode sumber implementasi referensi, kita akan melihat bagaimana rangkaian bekerja.

1. PagedListAdapter dalam metode getItem (int index) memanggil metode loadAround (int index) untuk PagedList:

/** * 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 melakukan pemeriksaan dan memanggil metode void tryDispatchBoundaryCallbacks (posting boolean):

 /** * 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. Dalam metode ini, kebutuhan untuk mengunduh bagian data berikut diperiksa dan permintaan pengunduhan terjadi:

 /** * 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. Akibatnya, semua panggilan masuk ke DataSource, tempat data diunduh dari database atau dari sumber lain:

 @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()); } } 

Sementara semuanya terlihat sederhana - ambil saja dan lakukan. Hanya bisnis:

  1. Buat implementasi PagedList (RealmPagedList) Anda sendiri yang akan bekerja dengan RealmModel;
  2. Buat implementasi PagedStorage (RealmPagedStorage) Anda sendiri, yang akan bekerja dengan OrderedRealmCollection;
  3. Buat implementasi DataSource (RealmDataSource) Anda sendiri yang akan bekerja dengan RealmModel;
  4. Buat adaptor Anda sendiri untuk bekerja dengan RealmList;
  5. Hapus yang tidak perlu, tambahkan yang diperlukan;
  6. Selesai

Kami menghilangkan detail teknis minor, dan inilah hasilnya - pustaka RealmPagination . Mari kita coba membuat aplikasi yang menampilkan daftar pengguna.

0. Tambahkan perpustakaan ke proyek:

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


1. Buat kelas Pengguna:

 @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. Buat Sumber Data:

 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. Buat adaptor:

 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. Buat 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. Inisialisasi daftar:

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

6. Buat fragmen dengan daftar:

 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. Selesai.

Ternyata menggunakan Realm sesederhana Kamar. Sergey memposting kode sumber perpustakaan dan contoh penggunaan . Anda tidak akan harus memotong sepeda lain jika Anda mengalami situasi yang sama.

Source: https://habr.com/ru/post/id467097/


All Articles