54

I'm using the support RecyclerView in my app, and I see the most bizarre thing. It doesn't display any items until I touch to scroll. Then, all of a sudden, the RecyclerView populates itself. I have verified that the list backing the adapter is populated, and that onCreatViewHolder and onBindViewHolder are never called until a touch event.

Here's how I set up the recyclerview:

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        Drawable divider = new ColorDrawable(ProfileInfo.getCurrentProfileColor());
        mInboxList.addItemDecoration(new DividerItemDecoration(getActivity(), divider, false));
        mInboxList.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
        mInboxList.setAdapter(new InboxAdapter(getActivity(), new ArrayList<Conversation>()));
        new AsyncTask<Void, Void, List<Conversation>{

              public List<Conversation> doInBackground(...){
                   //load the conversations
                   return conversations;
              }

              public void onPostExecute(List<Conversation> conversations){
                   ((InboxAdapter) mInboxList.getAdapter()).clear(false);
                   ((InboxAdapter) mInboxList.getAdapter()).addAll(conversations);

              }

        }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }

Here's a gist of my adapter:

public class InboxAdapter extends RecyclerView.Adapter<InboxAdapter.ViewHolder> {
    List<Conversation> mConversations; //initialized in contructor

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = mInflater.inflate(R.layout.inbox_item, parent, false);
        ViewHolder holder = new ViewHolder(v);
        Log.d(LOG_TAG, "oncreateviewholder : " + viewType); //never called when I first bind the adapter
        return holder;
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final Conversation item = mConversations.get(position);
        Log.d(LOG_TAG, "binding " + position);
        ...
    }

    @Override
    public int getItemCount() {
        Log.d(LOG_TAG, "item count: " + mConversations.size());
        return mConversations.size();
    }

    /**
     * Empties out the whole and optionally notifies
     */
    public void clear(boolean notify) {
        mConversations.clear();
        if (notify) {
            notifyDataSetChanged();
        }
    }

    public void addAll(List<Conversation> conversations) {
        mConversations.addAll(conversations);
        notifyDataSetChanged();
    }


}

Here's the layout file:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingTop="?attr/actionBarSize">


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

I'm using recyclerview-v7:21.0.3 running on a Moto X with version 4.4.4.

EDIT: Smooth scrolling at the end of the onPostExecute seems to resolve the issue:

if (mInboxList.getAdapter().getItemCount() > 0) {
    mInboxList.smoothScrollToPosition(0);
}
WindsurferOak
  • 4,861
  • 1
  • 31
  • 36

10 Answers10

10

If anyone else is having this issue using RxJava and Retrofit, I solved this issue by adding the .observeOn(AndroidSchedulers.mainThread()) operator into my method chain before subscribing. I had read this was taken care of by default, and thus not explicitly necessary, but I guess not. Hope this helps.

Example:

public void loadPhotos() {
    mTestPhotoService.mServiceAPI.getPhotos()
                                 .subscribeOn(Schedulers.io())
                                 .observeOn(AndroidSchedulers.mainThread())
                                 .subscribe(photoList -> mRecyclerActivity.OnPhotosLoaded(photoList));
}
Carter Hudson
  • 1,176
  • 1
  • 11
  • 23
5

In my case only this worked:

recyclerView.smoothScrollToPosition(arrayModel.size-1); // I am passing last position here you can pass any existing position

RecyclerView was showing data only when I scroll it. So instead of user to manually scroll I have added above line which will scroll the recyclerView programmatically .

Suraj Vaishnav
  • 7,777
  • 4
  • 43
  • 46
5

So at first, just like everybody else I just added:

recyclerView.smoothScrollToPosition(0)

and it worked pretty much good enough, however it wasn't nice and you had to remember to add it every time. and I then I followed @SudoPlz comment and answer on another question and it also worked too, you have to extend RecyclerView and override requestLayout:

private boolean mRequestedLayout = false;

@SuppressLint("WrongCall")
@Override
public void requestLayout() {
    super.requestLayout();
    // We need to intercept this method because if we don't our children will never update
    // Check https://stackoverflow.com/questions/49371866/recyclerview-wont-update-child-until-i-scroll
    if (!mRequestedLayout) {
        mRequestedLayout = true;
        this.post(() -> {
            mRequestedLayout = false;
            layout(getLeft(), getTop(), getRight(), getBottom());
            onLayout(false, getLeft(), getTop(), getRight(), getBottom());
        });
    }
}

while still, I would have preferred this to fixed after 4, 5 years, however, this was a good workaround, and you won't forget about them in your view.

Amin Keshavarzian
  • 3,646
  • 1
  • 37
  • 38
  • 1
    This should be an accepted answer. This might be no less haskish than smoothScrollToPosition, but it covers the case when there is 0 item in the adapter. – Benoit Oct 03 '20 at 15:03
0

i'm also suffering from same issue for 2 days now its done after add this line.

adapter.notifyDataSetChanged() //after notify adapter add this one mRecyclerView.smoothScrollToPosition(list.size-1)

HandyPawan
  • 1,018
  • 1
  • 11
  • 16
0

In my case it's only happen on Android 6. above this version I didn't had any problems. So what I've found to work is to use recyclerView.scrollBy(0, 0). Check it maybe it will work for you too.

user3193413
  • 575
  • 7
  • 10
0

Since this is still happening, here is an specific fixed instance in case it helps someone:

  • It happened only when the cell view contains a WebView.

The fix:

  • Call setIsRecyclable(false) from the ViewHolder
Benoit
  • 1,922
  • 16
  • 25
0

This is a bit late, but in my case I had a recylerview inside a viewpager (TabView) that was inside another viewpager (using BottomNavigationView) and smooth scrolling didn't change anything.

However, I noticed that I had the recylerview set with Visibility GONE when the layout was drawn, so the solution was:

  1. Setting the recyclerview to Visibility VISIBLE on xml layout (so it's draw completely when the TabView is set);
  2. When the adapter is set (linked to observable with room), i do:
new Handler(Looper.getMainLooper()).postDelayed(() -> {

     binding.recyclerView.scrollToPosition(0);

}, getResources().getInteger(android.R.integer.config_shortAnimTime));

0

None of the solutions worked for me! in my case, it just solved with this workaround:

    recyclerView.postDelayed(new Runnable() {
        @Override
        public void run() {

            recyclerView.setAdapter(adapter);

        }
    },1000);
faridfedora
  • 103
  • 1
  • 5
-1

You would have to run the stuff you do in onPostExecute on the UI thread so that the RecyclerView gets redrawn. That's why the smooth scrolling works, because that is running on the UI thread and therefore causing the view to redraw.

robocab
  • 777
  • 5
  • 15
-3

It's most likely because you're not calling the correct notification methods of RecyclerView.Adapter. You have a much more granular interface for this than you previously had in ListAdapters. For example, in addAll() you should call notifyItemRangeInserted(oldConversationsSize, conversations.size()) instead of notifyDataSetChanged

Marcus Forsell Stahre
  • 3,766
  • 1
  • 17
  • 14