title: RecyclerView infinite scroll with item insertion
body: <p>I have a difficult problem and I'm having problems also explaining it, so be patient please</p>
  
  <p>I have an <em>infinite scrolling</em> <code>RecyclerView</code> (only going top to bottom).</p>
  
  <p>This <code>RecyclerView</code> 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.</p>
  
  <p>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).</p>
  
  <p>My problem is that everything works fine, but when new items are added to the datasource during the scroll and I call the <code>NotifyDataSetChangd</code>, everything refreshes and my list blinks changing the current shown items.</p>
  
  <p>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.</p>
  
  <p>As you can see in the "before" column, at visible index 5, I will see the B item.</p>
  
  <p>Now a message arrives and the source is notified of the change refreshing everything.</p>
  
  <p><strong>position -- before -- after</strong></p>
  
  <pre><code>    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
  </code></pre>
  
  <p>What appens now: the list blinks and the item C is shown (index 5 of the after column).</p>
  
  <p>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.</p>
  
  <p><strong>This is the adapter (more or less it does all the work, I think it is the only significant code)</strong></p>
  
  <pre><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&lt;MessagesAdapter.MessagesViewHolder&gt; {
  
      private ArrayList&lt;SMSMessage&gt; SMSMessages;
      private int textSize;
      private Typeface custom_font;
      private int lastShownPosition;
      LinkedHashMap&lt;Integer, Integer&gt; offsets;
      Integer lastEntryKey;
  
      public MessagesAdapter(int textSize, Typeface custom_font, ArrayList&lt;SMSMessage&gt; SMSMessages) {
          this.SMSMessages = SMSMessages;
          this.textSize = textSize;
          this.custom_font = custom_font;
          this.lastShownPosition = 0;
          this.offsets = new LinkedHashMap&lt;&gt;();
          offsets.put(0, -1);
      }
  
      public void addItem(SMSMessage msg) {
          SMSMessages.add(msg);
          if (SMSMessages.size() &gt; 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&lt;&gt;();
          }
          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&lt;Integer, Integer&gt; entry :
                  offsets.entrySet()) {
              if (entry.getValue() == -1) {
                  return SMSMessages.get(position % SMSMessages.size());
              }
              if (position &gt;= entry.getKey() &amp;&amp; position &lt;= 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 &amp;&amp; SMSMessage.isValid()) {
                  try {
                      tvMsgText.setText(SMSMessage.getMessageText());
                  } catch (Exception ignored) {
                      tvMsgText.setText("");
                  }
              } else {
                  tvMsgText.setText("");
              }
          }
      }
  }
  </code></pre>
  
  <p>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.</p>
  
  <p>I tried to be clear, if not ask freely</p>
  
  <p>thanks for help</p>
  
  <p>I'm also open to new approaches, this is the one I used but it might be wrong, I don't really know</p>