1

I have a difficult problem and I'm having problems also explaining it, so be patient please

I have an infinite scrolling RecyclerView (only going top to bottom).

This RecyclerView shows messages received by the phone from the first to the last, and when the last one is reached, it shows again the first (scrolling only downside) and so on.

I implemented this solution with an auto-scroll function that always increment my position to be shown smoothly scrolling to the next. In the adapter, I get the actual position of the item in list and I bind it to my viewholder (ie: if I have 2 items, and the position to be shown is the 5th, I will get the 1st item with the logic of showing item this way: 1-2-1-2-1).

My problem is that everything works fine, but when new items are added to the datasource during the scroll and I call the NotifyDataSetChangd, everything refreshes and my list blinks changing the current shown items.

I try to explain the problem with an example: lets suppose my current visible index is the 5th in my infinite scroll, and my source has 2 items.

As you can see in the "before" column, at visible index 5, I will see the B item.

Now a message arrives and the source is notified of the change refreshing everything.

position -- before -- after

    0       A        A
    1       B        B
    2       A        C
    3       B        A
    4       A        B
    5       B        C
    6       A        A
    7       B        B
    8       A        C
    9       B        A

What appens now: the list blinks and the item C is shown (index 5 of the after column).

What I want: the list should go on as it is now and after the actual A-B rotation, it shows the C items newly added without blinking and refreshing everything.

This is the adapter (more or less it does all the work, I think it is the only significant code)

import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.Map;

import utilitapps.com.ememories.R;
import utilitapps.com.ememories.db_objects.SMSMessage;

public class MessagesAdapter extends RecyclerView.Adapter<MessagesAdapter.MessagesViewHolder> {

    private ArrayList<SMSMessage> SMSMessages;
    private int textSize;
    private Typeface custom_font;
    private int lastShownPosition;
    LinkedHashMap<Integer, Integer> offsets;
    Integer lastEntryKey;

    public MessagesAdapter(int textSize, Typeface custom_font, ArrayList<SMSMessage> SMSMessages) {
        this.SMSMessages = SMSMessages;
        this.textSize = textSize;
        this.custom_font = custom_font;
        this.lastShownPosition = 0;
        this.offsets = new LinkedHashMap<>();
        offsets.put(0, -1);
    }

    public void addItem(SMSMessage msg) {
        SMSMessages.add(msg);
        if (SMSMessages.size() > 1) {
            offsets.remove(lastEntryKey);
            offsets.put(lastEntryKey, lastShownPosition);

            //aggiungo poi l'ultima
            lastEntryKey = lastShownPosition + 1;
            offsets.put(lastEntryKey, -1);
        } else {
        }
        notifyDataSetChanged();
    }

    public int getActualItemCount() {
        if (SMSMessages == null) {
            SMSMessages = new ArrayList<>();
        }
        return SMSMessages.size();
    }

    @Override
    public int getItemCount() {
        return Integer.MAX_VALUE;
    }

    @NonNull
    @Override
    public MessagesAdapter.MessagesViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new MessagesAdapter.MessagesViewHolder(LayoutInflater.from(parent.getContext())
                .inflate(R.layout.recyclerview_item_message, parent, false));
    }


    @Override
    public void onBindViewHolder(@NonNull MessagesAdapter.MessagesViewHolder holder, int position) {
        lastShownPosition = position;
        holder.bind(getSMSMessage(position));
    }

    private SMSMessage getSMSMessage(int position) {
        if (SMSMessages.size() == 0) {
            return null;
        }
        int itemLastIndex = 1;
        for (Map.Entry<Integer, Integer> entry :
                offsets.entrySet()) {
            if (entry.getValue() == -1) {
                return SMSMessages.get(position % SMSMessages.size());
            }
            if (position >= entry.getKey() && position <= entry.getValue()) {
                return SMSMessages.subList(0, itemLastIndex).get(position % SMSMessages.subList(0, itemLastIndex).size());
            }
            itemLastIndex++;
        }
        return null;
    }

    class MessagesViewHolder extends RecyclerView.ViewHolder {

        TextView tvMsgText;

        MessagesViewHolder(View itemView) {
            super(itemView);
            tvMsgText = itemView.findViewById(R.id.tvMsgText);
            tvMsgText.setTextSize(textSize);
            tvMsgText.setTypeface(custom_font);
        }

        void bind(SMSMessage SMSMessage) {
            if (SMSMessage != null && SMSMessage.isValid()) {
                try {
                    tvMsgText.setText(SMSMessage.getMessageText());
                } catch (Exception ignored) {
                    tvMsgText.setText("");
                }
            } else {
                tvMsgText.setText("");
            }
        }
    }
}

as you can see I tried to implement a solution storing a map of indexes so I could fill my list with this example (following this logics: A B A B (item added but A already loaded) A B C A B C...) but it's not working and I'm not fully understanding why.

I tried to be clear, if not ask freely

thanks for help

I'm also open to new approaches, this is the one I used but it might be wrong, I don't really know

Pier Giorgio Misley
  • 5,305
  • 4
  • 27
  • 66
  • I didn't understand you well but, i guess that you want to keep the position of the recyclerview after updating items? The problem that you have is that when you update items it goes right to the top of the rv? – Joaquín Jul 06 '18 at 13:37
  • @JoaquinAlvarez thanks for commenting, yes, basically I want to keep the position after updating, but the problem is that the position in list is not the position in datasource (for example the position in list might be 5 and showing the first item) – Pier Giorgio Misley Jul 06 '18 at 13:39
  • hmmi don't know if this will work four you check this answer, you need to save the last position like int lastPosition = yourlist.size() -1; and then apply it to the code in the answer https://stackoverflow.com/questions/26875061/scroll-recyclerview-to-show-selected-item-on-top – Joaquín Jul 06 '18 at 13:41

0 Answers0