Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Android - notifyItemRangeInserted() is not updating the position in RecyclerView properly

Ask Question

I want to add new item(s) in a list of RecyclerView and always place it at the very top of the list. But unfortunately strange behavior occurs while updating the list I used getAdapterPosition() to monitor if index/position is getting update.

Default list: 0, 1, 2, 3, 4 is my first list

My Pagination rule is 5 items will be added every time a user scroll at the very bottom.

Now I want to add new item and place it at the top of list without using too much of memory that is why I do not want to use notifyDataSetChanged() at all. But after adding new item this is the result.

When new item is added: "this is the new item" > 0 , 0, 1, 2, 3, 4 The getAdapterPosition() is not getting update, instead it duplicates the index thus creating a two or more zeros index every time new item is added?

My understanding with notifyItemRangeInserted() is when new item added, it will update the remaining items in the list starting from what we pass in the first parameter of it which is the "positionStart" and update the next/rest of the item right after that positionStart.

This is my first query with Firestore that will get the first 5 items in onCreate method.

   //Load the first item(s) to display
      //Set a query according to time in milliseconds
      mQuery = mDatabase.collection("Announcements")
              .orderBy("time", Query.Direction.DESCENDING)
              .limit(5);
      //Getting all documents under Announcement collection with query's condition
      annon_listener = mQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
          @Override
          public void onEvent(final QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
              //If something went wrong
              if (e != null)
                  Log.w(TAG, "Listen failed.", e);
              //If any post exist put it to model and add it to List to populate the CardView
              //If data exist in the first 5 items then item should be loaded making our 'isFirstListLoaded' variable to be true
              if (!documentSnapshots.isEmpty()){
                  //If first item are loaded then every update post should be on the top not at the bottom
                  //This can only be called once to avoid confusion/duplication getting new item
                  if (isFirstListLoaded){
                      //Get the documents of last item listed in our RecyclerView
                      mLastSeen = documentSnapshots.getDocuments().get(documentSnapshots.size()-1);
                      //Clear the list first to get a latest data
                      announcementList.clear();
                  //Loop to read each document
                  for (DocumentChange doc : documentSnapshots.getDocumentChanges()){
                      //Only added document will be read
                      switch (doc.getType()){
                          case ADDED:
                              //Call the model to populate it with document
                              AnnouncementModel annonPost = doc.getDocument().toObject(AnnouncementModel.class)
                                      .withId(doc.getDocument().getId());
                              //To retrieve all post data in the same time we need to place this if else over here
                              //So user data and his/her post will be retrieve at the same time
                              //This can only be called once to avoid confusion getting new item(s)
                              if (isFirstListLoaded){
                                      announcementList.add(annonPost);
                                      announcementRecyclerAdapter.notifyDataSetChanged();
                              if (isJustDelete)
                                  isJustDelete = false;
                                  //If someone just remove a post then do nothing and return the state to false
                                  //This will be called once a user added new item to database and put it to top of the list
                              else if (!isFirstListLoaded && !isJustDelete){
                                  //Before adding new item to the list lets save the previous size of the list as a reference
                                  int prevSize = announcementList.size();
                                  //This will be called only if user added some new post
                                  announcementList.add(0, annonPost);
                                  //Update the Recycler adapter that new data is added
                                  announcementRecyclerAdapter.notifyItemRangeInserted(0, announcementList.size() - prevSize);
                              //Just checking of where's the data fetched from
                              String source = documentSnapshots.getMetadata().isFromCache() ?
                                      "Local" : "Server";
                              Log.d(TAG, "Data fetched from " + source + "\n" + doc.getDocument().getData());
                              break;
                  //After the first item/latest post was loaded set it to 
                 false it means that first items are already fetched
                  isFirstListLoaded = false;
              //If no post exist then display no content TextView
              else if (announcementList.isEmpty()){
                  noContent.setVisibility(View.VISIBLE);
                  annonRecyclerView.setVisibility(View.GONE);

And when a user scroll down and reach the bottom this method will be called to fetched the next 5 items available in database.

      //Load more queries
private void loadMoreList(){
    //Load the next item(s) to display
    //Set a query according to time in milliseconds
    //This time start getting data AFTER the last item(s) loaded
    mQuery = mDatabase.collection("Announcements")
                .orderBy("time", Query.Direction.DESCENDING)
                .startAfter(mLastSeen)
                .limit(5);
    //Getting all documents under Announcement collection with query's condition
    annon_listener = mQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
            @Override
            public void onEvent(final QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
                //If something went wrong
                if (e != null)
                    Log.w(TAG, "Listen failed.", e);
                //If no more item(s) to load
                if (documentSnapshots.isEmpty()){
                    isFullyLoaded = true;
                    messenger = Snackbar.make(mPullToRefreshView,"No more item(s) to load.",Snackbar.LENGTH_LONG)
                            .setActionTextColor(Color.WHITE)
                            .setAction("Dismiss", new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    messenger.dismiss();
                    messenger.show();
                else{
                    //If more data exist then update our 'mLastSeen' data
                    //Update the last list shown in our RecyclerView
                    mLastSeen = documentSnapshots.getDocuments().get(documentSnapshots.size()-1);
                    //Loop to read each document
                    for (DocumentChange doc : documentSnapshots.getDocumentChanges()){
                        //Only added document will be read
                        switch (doc.getType()){
                            case ADDED:
                                //Call the model to repopulate it with document
                                AnnouncementModel annonPost = doc.getDocument().toObject(AnnouncementModel.class)
                                        .withId(doc.getDocument().getId());
                                //This if condition is use to avoid rumbling the item position when deleting some item
                                if (isJustDelete)
                                    isJustDelete = false;
                                else if (!isJustDelete && !isFullyLoaded){
                                    int prevSize = announcementList.size();
                                    //Add any new item(s) to the List
                                    announcementList.add(announcementList.size(), annonPost);
                                    //Update the Recycler adapter that new data is added
                                    //This trick performs recycling even though we set nested scroll to false
                                    announcementRecyclerAdapter.notifyItemRangeInserted(announcementList.size(), announcementList.size() - prevSize);
                                //Just checking of where's the data fetched from
                                String source = documentSnapshots.getMetadata().isFromCache() ?
                                        "Local" : "Server";
                                Log.d(TAG, "Data fetched from " + source + "\n" + doc.getDocument().getData());
                                break;
                            case REMOVED:
                                break;
                            case MODIFIED:
                                break;

How can I solve this without using notifyDataSetChange() or is it even possible? Another thing is that when I use notifyItemRangeInserted(0, list.size()); It throws an error: IndexOutOfBoundsException: Inconsistency detected.

Honestly this doesn't make sense. The docs clearly say that the second parameter is the number of items that are inserted (in your case 5?). Passing the total list size will just result in a wrong update process. – reVerse Jun 11, 2018 at 14:30 No it should be like this: At which position in the list are the new items added? -> first parameter; How many items are added? -> second parameter. If I understand correctly in your case it's always 0 and 5. Or in case your adding the items at the end of the list it would be notifyItemRangeInserted(previousList.size(), 5). – reVerse Jun 11, 2018 at 14:45 Yes I also adding new items at the bottom. The process is like this: if recycler cannot scroll vertically it will load 5 new items at the bottom if my list is 0 - 4 I will add new items where index = list.size() so it will start at index 5 and ends with index 9 and so on. – user9113430 Jun 11, 2018 at 14:50 But how about when 10 items are loaded then I will add new item and place it at the top which is index 0. Is it all the same? – user9113430 Jun 11, 2018 at 15:00

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid …

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.