Gaket
Gaket

Reputation: 6829

RecyclerView notifyDataSetChanged() freezes UI without ANR

We have a NestedScrollView that contains two different RecyclerView both working with vertical scroll. The scroll layout lays inside SwipeRefreshLayout.

Upd: We know about getItemViewType(pos) method and use it in other places. But here we have complex logic that will be further divided into 2 different screens, therefore we have two separate presenters for each of RecyclerView and combining them into one and separating again in a month is not an option.

From some moment, we noticed that updating the screen using swipe to refresh starts to freeze the UI: skipped frames in logs, impossibility to make any operations with UI, freezing progress bar. It takes up to five seconds, but we don't get ANR. It works well in the beginning and freezes at the end. If we remove notifyDataSetChanged() in adapter, everything looks good.

We tried to remove one of recycler views, to remove all the operations from ViewHolder.onCreate() and onBindViewHolder() but it didn’t help. Also, we cross-checked if the problems come from some data processing (we use RxJava 2 to manipulate threading) and we see that all operations with networking, db and data processing are made in non-UI thread and are already finished to the moment when lag starts.

Here is the layout:

<android.support.v4.widget.SwipeRefreshLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/swipe_refresh"
    android:layout_width="match_parent"
    android:layout_height=«match_parent"
    android:orientation="vertical"
    >
<android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="wrap_content»
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation=«vertical"
        android:paddingBottom="@dimen/spacing_bigger»>
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_chats"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white_bg"
            />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycle_friends"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/white_bg"
            />

    </LinearLayout>

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

Upvotes: 2

Views: 6982

Answers (3)

iDeveloper
iDeveloper

Reputation: 1725

I was struggling with this too, finally came to this idea that I can't use RecyclerView inside nestedscrollView WITH notifyData properly so I did it this way:

I put CollapsingToolbarLayout inside AppBarLayout inside CoordinatorLayout . then I put RecyclerView header inside CollapsingToolbarLayout and the RecyclerView inside CoordinatorLayout below AppBarLayout.

and also I used addOnScrollListener method of RecyclerView TO load new Items for RecyclerView after pagination.

this is my xml file :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/
 android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff">

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/activity_txt_news_detail.app_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#00ffffff"
            app:elevation="0dp">

            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/collapsingToolbarLayout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:expandedTitleMarginStart="64dp"
                app:layout_scrollFlags="scroll|snap">


                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">

                    // all other views
                    // as header of
                    // recyclerview comes here
                </LinearLayout>

                <!--todo tgs : here top-->


            </android.support.design.widget.CollapsingToolbarLayout>

        </android.support.design.widget.AppBarLayout>

        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
             app:layout_behavior="@string/
             appbar_scrolling_view_behavior">


            <android.support.v7.widget.RecyclerView
                android:id="@+id/home_news_list"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                tools:listitem="@layout/row_news_txt_small" />

        </RelativeLayout>


    </android.support.design.widget.CoordinatorLayout>
</LinearLayout>

</FrameLayout>

this line of code app:layout_behavior="@string/appbar_scrolling_view_behavior" is very important it should be in recyclerview container layout

please inform me if it was useful for you . all the best

Upvotes: 5

MadScientist
MadScientist

Reputation: 2164

Why are you adding a nestedScrollLayout and a linearLayout and 2 recyclerViews.

All of this can be achieved with just the swipeRefreshLayout and RecyclerView

Try looking on how to add different views based on position or type of data. so technicaly you will have one recyclerView and one adapter only but the adapter will handle both the cases (friends and chats) and append data to the view(recyclerView).

And since RecyclerView also extends NestedScrollView keep a RecyclerView inside a NestedScrollView will make your scrolling jittery, you will then have to add isScrollContainer=true etc. flags. So dont do it this way!

Upvotes: 0

Gaket
Gaket

Reputation: 6829

It looks like NestedScrollView with layout_height="wrap_content" breaks all the optimizations of children RecyclerViews. It gives them infinite space regardless of the constraints from the parent view (SwipeRefreshLayout) that has layout_height="match_parent". As a result, RecyclerView creates view holders for all the models in adapter at once. Logging shows, that it takes 20-60ms for each of them what brings 2-5 second lag for 100 rows.

We added logging to onCreateViewHolder() and noticed, that there are tens (almost a hundred) of calls to it, whereas there is only about ten elements on the screen. Changing NestedScrollView's layout_height to "match_parent" fixed the problem.

Upvotes: 2

Related Questions