باستخدام مكتبة الترحيل مع عالم

في أحد اجتماعات قسم Android ، سمعت كيف قام أحد مطورينا بإنشاء ليب صغير يساعد في إعداد قائمة "لا نهاية لها" عند استخدام Realm ، مع الحفاظ على "التحميل الكسول" والإشعارات.

لقد صنعت وكتبت مشروع مقال ، وهو دون تغيير تقريبًا ، أشاركه معك. من جانبه ، وعد بأنه سيشغل المهام ويأتي في تعليقات إذا ظهرت أسئلة.

قائمة لا نهاية لها والحلول الجاهزة


تتمثل إحدى المهام التي نواجهها في عرض المعلومات في قائمة ، عند التمرير خلالها ، يتم تحميل البيانات وإدراجها بشكل غير مرئي للمستخدم. بالنسبة للمستخدم ، يبدو أنه سينتقل إلى قائمة لا حصر لها.

الخوارزمية هي تقريبا ما يلي:

  • نحصل على بيانات من ذاكرة التخزين المؤقت للصفحة الأولى ؛
  • إذا كانت ذاكرة التخزين المؤقت فارغة - نحصل على بيانات الخادم ، ونعرضها في القائمة والكتابة إلى قاعدة البيانات ؛
  • إذا كان هناك ذاكرة تخزين مؤقت ، فقم بتحميلها في القائمة ؛
  • إذا وصلنا إلى نهاية قاعدة البيانات ، فإننا نطلب البيانات من الخادم ، ونعرضها في القائمة والكتابة إلى قاعدة البيانات.

المبسطة: لعرض القائمة ، يتم استقصاء ذاكرة التخزين المؤقت لأول مرة ، وتكون الإشارة إلى تحميل بيانات جديدة هي نهاية ذاكرة التخزين المؤقت.

لتنفيذ التمرير الذي لا نهاية له ، يمكنك استخدام حلول جاهزة:


نحن نستخدم Realm كقاعدة بيانات للهاتف المحمول ، وبعد أن جربنا جميع الأساليب المذكورة أعلاه ، توقفنا عن استخدام مكتبة Paging.

للوهلة الأولى ، تعد Android Paging Library حلاً ممتازًا لتنزيل البيانات ، وعند استخدام sqlite مع Room ، فهي ممتازة كقاعدة بيانات. ومع ذلك ، عند استخدام Realm كقاعدة بيانات ، فإننا نفقد كل شيء اعتدنا عليه - كسلان إعلامات التحميل وتغيير البيانات . لم نكن نريد التخلي عن كل هذه الأشياء ، ولكن في نفس الوقت استخدم مكتبة Paging.

ربما لسنا أول من يحتاج إليها


أسفر البحث السريع عن حل على الفور - مكتبة مملكة Realmy . بعد دراسة سريعة ، اتضح أن هذا الحل لا يناسبنا - لا تدعم المكتبة التحميل الكسول أو الإخطارات. اضطررت لإنشاء بلدي.

لذلك ، المتطلبات هي:

  1. الاستمرار في استخدام عالم ؛
  2. حفظ التحميل كسول للعالم.
  3. حفظ الإخطارات.
  4. استخدم مكتبة Paging لتحميل البيانات من قاعدة البيانات وصفحات ترقيم الصفحات من الخادم ، تمامًا كما تقترح مكتبة Paging.

من البداية ، دعونا نحاول معرفة كيفية عمل مكتبة Paging ، وما يجب القيام به لجعلنا نشعر بالرضا.

باختصار - تتكون المكتبة من المكونات التالية:

مصدر البيانات - الفئة الأساسية لتحميل صفحة البيانات حسب الصفحة.
له تطبيقات: PageKeyedDataSource و PositionalDataSource و ItemKeyedDataSource ، لكن الغرض منها ليس مهمًا بالنسبة لنا الآن.

PagedList - قائمة تقوم بتحميل البيانات في مجموعات من DataSource. ولكن بما أننا نستخدم Realm ، فإن تحميل البيانات على دفعات لا يناسبنا.
PagedListAdapter - الفئة المسؤولة عن عرض البيانات التي تم تحميلها بواسطة PagedList.

في الكود المصدري للتطبيق المرجعي ، سنرى كيف تعمل الدائرة.

1. يستدعي PagedListAdapter في أسلوب getItem (مؤشر int) loadAround (int index) لطريقة 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 ينفذ الشيكات ويستدعي طريقة الفراغ 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. تم

اتضح أن استخدام عالم بسيط مثل الغرفة. نشر سيرجي شفرة المصدر للمكتبة ومثال للاستخدام . لن تضطر إلى قطع دراجة أخرى إذا واجهت وضعا مماثلا.

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


All Articles