Mathieu
Mathieu

Reputation: 1713

Is SwipeRefreshLayout compatible with an adapter that has multiple viewType?

I am making a list, that will get more items when I go to the bottom of the list. I want this list to be refreshable too, so I need a SwipeRefreshLayout.

Let's say I have an adapter like this :

class OrdersListAdapter(val selected : (Order) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    fun addOrders(orders: ArrayList<Order>) {
        this.orders.addAll(orders)
        notifyItemRangeInserted(this.orders.size - orders.size + FIRST_ITEM_INDEX, orders.size)
    }

    fun setFirstOrders(orders: ArrayList<Order>) {
        this.orders = orders
        notifyDataSetChanged()
    }

    override fun getItemCount(): Int {
        return (orders.size + 1 + FIRST_ITEM_INDEX)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            TYPE_ORDERS -> OrdersViewHolder(DataBindingUtil.inflate<OrdersItemBinding>(LayoutInflater.from(parent.context),
                R.layout.orders_item, parent, false).apply {
                viewModel = OrdersViewModel()
            })
            else -> LoadingMoreOrdersViewHolder(DataBindingUtil.inflate<LoadingMoreOrdersItemBinding>(LayoutInflater.from(parent.context),
                    R.layout.loading_more_orders_item, parent, false).apply {
                viewModel = LoadingMoreOrdersViewModel()
            })
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            TYPE_ORDERS -> (holder as? OrdersViewHolder)?.setData(orders[position - FIRST_ITEM_INDEX])
            TYPE_LOADING -> (holder as? LoadingMoreOrdersViewHolder)?.setState(loading, error, noMoreItems)
        }
    }

    override fun getItemViewType(position: Int): Int =
            if (position == FIRST_ITEM_INDEX - 1) TYPE_HEADER else if (position == itemCount - 1) TYPE_LOADING else TYPE_ORDERS

Well if I put my RecyclerView like this, inside a SwipeRefreshLayout :

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/orders_rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layoutManager="android.support.v7.widget.LinearLayoutManager" />

    </android.support.v4.widget.SwipeRefreshLayout>

This will display all items, but the refreshLayout will not work : If I pull my view, nothing happens.

But if I do not set any loading layout inside my adapter (the one at bottom of the list, to load more items, inflated from R.layout.loading_more_orders_item), then the SwipeRefreshLayout will work properly.

How can I have 2 view types in my adapter, AND set my RecyclerView in a SwipeRefreshLayout ?

EDIT :

Here is my onRefreshListener :

refreshLayout = binding.swipeRefreshLayout

refreshLayout?.setOnRefreshListener { reloadOrders() }

And in reloadOrders() :

private fun reloadOrders() {
    viewModel.setLoading()
    refreshLayout?.isRefreshing = false
    Observable.timer(1, TimeUnit.SECONDS)
            .subscribe { getOrders() }
}

Upvotes: 0

Views: 278

Answers (3)

Mathieu
Mathieu

Reputation: 1713

Okay so thanks to @r2rek, I found a solution that worked for me :

In the code, we create a class that extends LinearLayoutManager and overrides method canScrollVertically() to always return false :

inner class MyLinearLayout(context: Context) : LinearLayoutManager(context) {
    override fun canScrollVertically(): Boolean {
        return (false)
    }
}

Then, when we init our RecyclerView, we apply this class to our layoutManager :

ordersRv?.layoutManager = MyLinearLayout(this)

This will allow you to refresh, however you will not be able to scroll in your RecyclerView (duh).
To fix this, add a NestedScrollView in your xml, as following :

<android.support.v4.widget.SwipeRefreshLayout
    android:id="@+id/refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_marginTop="8dp"
    android:layout_marginBottom="8dp"
    android:visibility="@{viewModel.ordersVisibility}"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/toolbar">

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/orders_rv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    </android.support.v4.widget.NestedScrollView>

</android.support.v4.widget.SwipeRefreshLayout>

You can now scroll in your RecyclerView, and pull to refresh with your SwipeRefreshLayout.

I think this method is kinda hacky, so if someone have a better solution, feel free to tell us !

Upvotes: 0

r2rek
r2rek

Reputation: 2253

I believe that what happens is the recyclerview takes over the ontouch events from swiperefreshlayout. You could try overwriting linearlayout and its canScrollVertically() method to return false. Cheers!

Upvotes: 1

Dimitar Stoyanov
Dimitar Stoyanov

Reputation: 354

Answering your question, compatibility is not the issue you're having. Not sure of the size of your layout, as its height is 0dp. I'd play around with the view sizes and view hierarchy.

Upvotes: 0

Related Questions