Android Библиотека разбивки на страницы: RecyclerView переходит наверх при вызове loadAfter - PullRequest
1 голос
/ 24 апреля 2020

Я использую библиотеку Androidx Paging с RecyclerView для разбивки на страницы. Проблема, с которой я сталкиваюсь, заключается в том, что, когда я прокручиваю RecyclerView, после указанного числа PAGE_SIZE, вызывается loadAfter, и после получения новых данных мой RecyclerView прокручивается вверх (первый элемент) вместо дальнейшей прокрутки вниз. Я отладил свой код и увидел, что onBindViewHolder моего RecyclerView также вызывается с позиции 0. Итак, я думаю, что все данные обновляются. В идеале этого не должно происходить.

Версия библиотеки: реализация 'androidx.paging: пейджинговая среда выполнения: 2.1.2'

Вот код:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/tools">

    <data>

        <import type="android.view.View" />
        <variable
            name="viewModel"
            type="com.xyz.viewmodel.orders.OrderListViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:background="@color/colorWindowsSecondaryBg"
        android:layout_height="match_parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/_10sdp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <!--List-->
            <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
                android:id="@+id/srlFav"
                android:layout_width="match_parent"
                android:layout_height="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                bind:isRefreshing="@{viewModel.refreshVisibility}"
                bind:onRefreshListener="@{viewModel.swipeRefreshListener}">

                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rvOrders"
                    android:layout_width="match_parent"
                    android:layout_height="0dp"
                    bind:listitem="@layout/item_order" />

                <androidx.constraintlayout.widget.Barrier
                    android:id="@+id/barrierVisibility"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:barrierAllowsGoneWidgets="true"
                    app:barrierDirection="top"
                    app:constraint_referenced_ids="clBottomLoading" />

                <androidx.constraintlayout.widget.ConstraintLayout
                    android:id="@+id/clBottomLoading"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/_10sdp"
                    android:background="@android:color/transparent"
                    android:paddingTop="@dimen/_2sdp"
                    android:visibility="@{viewModel.bottomProgressVisibility &amp;&amp; !viewModel.refreshVisibility? View.VISIBLE : View.GONE,default=gone}"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/barrierVisibility">

                    <com.github.ybq.android.spinkit.SpinKitView
                        android:id="@+id/skv_loading_bottom"
                        style="@style/SpinKitView.ThreeBounce"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_gravity="center"
                        android:background="@android:color/transparent"
                        app:SpinKit_Color="@color/colorPrimaryDark"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent" />
                </androidx.constraintlayout.widget.ConstraintLayout>
            </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
        </androidx.constraintlayout.widget.ConstraintLayout>

        <!--No data layout-->
        <include
            android:id="@+id/includeError"
            layout="@layout/include_layout_error"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:visibility="@{!viewModel.progressVisibility &amp;&amp; viewModel.errorVisibility ? View.VISIBLE : View.GONE}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:message="@{viewModel.errorString}" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

OrderListDataSource

class OrderListDataSource(
    private val orderRepository: OrderRepository,
    private val coroutineScope: CoroutineScope?,
    private var showProgress: Boolean = true
) : PageKeyedDataSource<Int, OrdersItem>() {

    val ordersLiveData: MutableLiveData<Resource<ResOrders>> = MutableLiveData()

    override fun loadInitial(
        params: LoadInitialParams<Int>,
        callback: LoadInitialCallback<Int, OrdersItem>
    ) {

        if (BaseApp.instance?.let { NetworkUtils.isNetworkAvailable(it) } == true) {

            if (showProgress) {
                ordersLiveData.postValue(Resource.Loading(EnumLoading.LOADING_FIRST_PAGE))
            }

            coroutineScope?.launch {
                val result = orderRepository.getOrders(1)

                if (result is ResOrders) {
                    ordersLiveData.postValue(Resource.Success(result))
                    result.orders?.let {
                        callback.onResult(it, null, 2)
                    }

                } else if (result is BaseError) {
                    ordersLiveData.postValue(Resource.Error(result))
                }
            }
        } else {
            ordersLiveData.postValue(Resource.Loading(EnumLoading.LOADING_CLOSE))
        }
    }

    override fun loadAfter(
        params: LoadParams<Int>,
        callback: LoadCallback<Int, OrdersItem>
    ) {

        if (BaseApp.instance?.let { NetworkUtils.isNetworkAvailable(it) } == true) {
            if (showProgress) {
                ordersLiveData.postValue(Resource.Loading(EnumLoading.LOADING_MORE))
            }

            coroutineScope?.launch {
                val result = orderRepository.getOrders(params.key)
                if (result is ResOrders) {
                    ordersLiveData.postValue(Resource.Success(result))
                    result.orders?.let { callback.onResult(it, params.key + 1) }

                } else if (result is BaseError) {
                    ordersLiveData.postValue(Resource.Error(result))
                }
            }

        } else {
            ordersLiveData.postValue(Resource.Loading(EnumLoading.LOADING_CLOSE))
        }
    }

    override fun loadBefore(
        params: LoadParams<Int>,
        callback: LoadCallback<Int, OrdersItem>
    ) {
    }
}

OrderListDataSourceFactory

class OrderListDataSourceFactory(
    private val productRepository: OrderRepository, private val coroutineScope: CoroutineScope?
) : DataSource.Factory<Int, OrdersItem>() {

    val ordersLiveData = MutableLiveData<OrderListDataSource>()

    private var showProgress: Boolean = true
    private var orderListDataSource: OrderListDataSource? = null

    override fun create(): DataSource<Int, OrdersItem> {
        orderListDataSource = OrderListDataSource(
            productRepository, coroutineScope,
            showProgress
        )
        ordersLiveData.postValue(orderListDataSource)
        return orderListDataSource!!
    }
}

OrderListViewModel

private val pageSize = 10
val listHasData = MutableLiveData<Boolean>().apply { value = true }
private val orderListDataSourceFactory: OrderListDataSourceFactory =
    OrderListDataSourceFactory(orderRepository, viewModelScope)

init {
    val config = PagedList.Config.Builder()
        .setPageSize(pageSize)
        .setInitialLoadSizeHint(pageSize * 2)
        .setEnablePlaceholders(false)
        .build()
    orderItem = LivePagedListBuilder(orderListDataSourceFactory, config).build()
}

OrdersAdapter

class OrdersAdapter constructor(private val itemTouchListener: ItemTouchListener) :
    PagedListAdapter<OrdersItem, OrderViewHolder>(DiffUtilCallBack()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OrderViewHolder {
        val mItemProductBinding = DataBindingUtil.inflate<ViewDataBinding>(
            LayoutInflater.from(parent.context),
            R.layout.item_order,
            parent,
            false
        )
        return OrderViewHolder(mItemProductBinding)
    }

    override fun onBindViewHolder(holder: OrderViewHolder, position: Int) {
        getItem(position)?.let {
            holder.bind(it)

            val itemClickListener: View.OnClickListener = View.OnClickListener { _ ->
                itemTouchListener.onOrderClick(position, it)

            }
            holder.setItemClickListener(itemClickListener)
        }
    }

    class DiffUtilCallBack : DiffUtil.ItemCallback<OrdersItem?>() {

        override fun areItemsTheSame(
            oldItem: OrdersItem,
            newItem: OrdersItem
        ): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(
            oldItem: OrdersItem,
            newItem: OrdersItem
        ): Boolean {
            return oldItem.id == newItem.id
                    && oldItem.orderDate.equals(newItem.orderDate)
                    && oldItem.orderId.equals(newItem.orderId)
                    && oldItem.totalPrice == newItem.totalPrice
                    && oldItem.deliveryStatus.equals(newItem.deliveryStatus)
        }
    }

    interface ItemTouchListener {
        fun onOrderClick(position: Int, ordersItem: OrdersItem)
    }
}

class OrderViewHolder constructor(private val binding: ViewDataBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(ordersItem: OrdersItem) {
        binding.setVariable(BR.order, ordersItem)
        binding.executePendingBindings()
    }

    fun setItemClickListener(clickListener: View.OnClickListener) {
        binding.root.lOrder.setOnClickListener(clickListener)
    }

}

OrderListingFragment

mViewModel.orderItem.observe(this, Observer {
    ordersAdapter.submitList(it)
})}

mViewModel.getOrders().observe(this, Observer {
     ...
}

Может кто-нибудь помочь мне с этим?

Заранее спасибо!

1 Ответ

0 голосов
/ 24 апреля 2020

Проблема была из-за Skeleton, который я использовал с RecyclerView.

mSkeleton?.hide()

Эта строка вызывалась каждый раз, когда я получал ответ API. В идеале скелет будет отображаться изначально. Итак, эта строка должна вызываться только один раз. Я обработал этот случай, и теперь он работает как положено.

...