20

I am trying to use RecyclerView to create a chat application. I am using a LinearLayoutManager with setReverseLayout(true).

When I am scrolled all the way to the bottom (which is the dataset start = newest message) and a new message is inserted into the dataset, the item appears at the bottom of the list as expected (the view is scrolled up to make room for the new item).

The problem I have is when I have scrolled up to see the older messages. When a new message is inserted to the beginning of the dataset, the view is scrolled up by approximately one message height, even though that message isn't even rendered since it's out of the viewport's range.

How can I keep the scrolling behavior, when the view is scrolled to the bottom, but disable it when I have scrolled to the older messages?

UPDATE: I also made a small app, where this problem is reproduced: https://github.com/ehehhh/RecyclerViewProblem

UPDATE 2: I committed the fix that worked to the repo I made as well.

Relevant code (hopefully):

compile 'com.android.support:recyclerview-v7:24.2.0'

XML:

<android.support.v7.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipToPadding="false"
            android:paddingBottom="8dp"
            android:paddingTop="8dp"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

RecyclerView init code:

layoutManager = new LinearLayoutManager(context);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
layoutManager.setReverseLayout(true);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setScrollContainer(true);
recyclerView.setLayoutAnimation(null);
recyclerView.setItemAnimator(null);
adapter = new ChatAdapter(...);
recyclerView.setAdapter(adapter);

Adapter:

public class ChatAdapter extends RecyclerView.Adapter<ChatViewHolder> {

    private List<MessageWrapper> dataset;

    public ChatAdapter(List<MessageWrapper> dataset, ...) {
        this.dataset = dataset;
        setHasStableIds(true);
    }

    ...

    @Override
    public long getItemId(int position) {
        return dataset.get(position).getId();
    }

    @Override
    public int getItemCount() {
        return dataset.size();
    }

    public void datasetChanged(List<MessageWrapper> dataset) {
        this.dataset = dataset;
        notifyDataSetChanged();
    }
}

When a new item is added to the dataset, I just call the datasetChanged method in the adapter.

ehehhh
  • 1,068
  • 3
  • 16
  • 27
  • do you update the whole data set? or do you just insert new row? – Hala.M Sep 05 '16 at 09:12
  • I just add an item to the beginning of the list and then I call `notifyDatasetChanged()`. – ehehhh Sep 05 '16 at 09:19
  • @ehehhh I am having same issue with setStackFromEnd(true) ? Any clue on that ? – ADM Dec 19 '17 at 10:52
  • Hey, @ADM, Ever since I started using DiffUtil, I haven't had this problem anymore. – ehehhh Dec 19 '17 at 14:23
  • Hey thx for reply . My problem is resolved now . I have no idea what was the exact issue it was happening only on Android N and above . But resolved now . Whats the DiffUtil BTW ? – ADM Dec 19 '17 at 16:22
  • It is basically a tool that helps calculating updates for RecyclerView Adapter. Here's the documentation: https://developer.android.com/reference/android/support/v7/util/DiffUtil.html – ehehhh Dec 19 '17 at 20:19

2 Answers2

6

in Recycler view using notifyDataSetChanged is redundant if you know the items changed you can use

notifyItemInserted(position) 

in this particular case what worked was

 notifyItemInserted(0);

or

 notifyItemRangeInserted(positionStart, newItems.size() - 1)

this will only rebind the views in this range

check https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#notifyItemInserted(int)

Hala.M
  • 746
  • 6
  • 14
  • 1
    Thank you for your answer, I tried your way, the behaviour remained the same. It makes sense I think, since I'm using `setHasStableIds(true)`, which should basically calculate the deltas "behind the scenes" and call the `notifyItemRangeInserted()` or whatever is needed automatically. – ehehhh Sep 05 '16 at 10:59
  • did you try setHasStableIds(false) and test it just out of curiosity did something change? – Hala.M Sep 05 '16 at 11:20
  • Yes, I'm getting the same results. – ehehhh Sep 05 '16 at 11:26
  • 1
    I created a small app which reproduces this problem, check it out if you have the time: https://github.com/ehehhh/RecyclerViewProblem – ehehhh Sep 05 '16 at 12:00
  • 2
    I played around with the test app I created, and it seems to work with `notifyItemInserted(0)`, I'm marking your answer as the correct one. Thank you! – ehehhh Sep 05 '16 at 12:43
0

I had a similar issue. I was making chat application, and my RecyclerView was always displaying rows from the top, and I wanted it to go to the bottom to display last inserted message. This is what worked for me, recyclerView.scrollToPosition(messages.size()-1) added in ChildEventListener:

public class MainActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private MessageAdapter mMessageAdapter;
    private List<Message> messages;

    private FirebaseDatabase mFirebaseDatabase;
    private DatabaseReference mMessagesDatabaseRef;

    ....


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mFirebaseDatabase = FirebaseDatabase.getInstance();
        mMessagesDatabaseRef = mFirebaseDatabase.getReference().child("messages");


        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);


        // Initialize message ListView and its adapter
        messages = new ArrayList<>();
        mMessageAdapter = new MessageAdapter(messages);



        RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(mLayoutManager);

        recyclerView.setAdapter(mMessageAdapter);

       .....   

        mMessagesDatabaseRef.addChildEventListener(new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String s) {


                messages.add(dataSnapshot.getValue(Message.class));

                recyclerView.scrollToPosition(messages.size()-1);
                mMessageAdapter.notifyDataSetChanged();

            }

            ......


        });

    }



}
Nemus
  • 3,405
  • 11
  • 32
  • 46