RecyclerView duplicating items when starting the application

0

When starting the fragment and loading the recyclerview, it duplicates the items.

 private void loadData() {
    if (categoriaList.size() > 0)
        categoriaList.clear();
    miprogress.setVisibility(View.VISIBLE);
    db.collection("categoria")
                .get()
                .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
                    @Override
                    public void onComplete(@NonNull Task<QuerySnapshot> task) {
                        for (DocumentSnapshot doc : task.getResult()) {
                            CategoriaModel cat = new CategoriaModel(doc.getString("nombre"), doc.getString("url"), doc.getId());
                            categoriaList.add(cat);
                        }
                        adapter = new CategoriaAdapter((MainActivity) getActivity(), categoriaList);
                        listItem.setAdapter(adapter);
                        miprogress.setVisibility(View.GONE);
                    }
                })
                .addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {
                        Toast.makeText(getContext(), "" + e.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                });
}

In my onresume and onactivity created I have it this way:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    listItem = getActivity().findViewById(R.id.recycler_categorias);
    miprogress =  getView().findViewById(R.id.circularProgress);
    layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
    listItem.setHasFixedSize(true);
    listItem.setLayoutManager(layoutManager);
}

@Override
public void onResume() {
    super.onResume();
    loadData();

}

The funny thing here is that when I change Activity or another fragment, the list does not duplicate and it looks good. I do not know why it only happens when you start the application

    
asked by Daniel Jaimes 25.04.2018 в 20:23
source

1 answer

2

The problem is that you must be careful to populate a Adapter in the life cycle of the Fragment onResume() . This overwritten method is usually very dangerous when loading tasks if it is not validated correctly. You are committing two important errors.

1. You are populating the data in onResume() , which can be disparado multiple times before the data is populated or has a chance to arrive. Remember that the data takes a while to arrive, so when it is triggered multiple times, the result may arrive the same number of times the event is triggered before it adds any results. As a result, the data will double.

onResume() can be called multiple times if the Fragments are not implemented correctly in Activity . Like turning the screen off and on quickly, etc ...

Possible solutions :

  • Declare a variable boolean that validates your data load. for example:

private boolean isDataLoaded;

@Override
public void onResume() {
    super.onResume();
    if(!isDataLoaded){
       loadData();
    }
}

Then at the beginning of your method loadData() returns the variable isDataLoaded = true and in the onFailure of the task returns it false , this in case you have a refresh that wants to call the method again.

  • Another option would be to move loadData() to onCreateView of the Fragment.

2. You are committing the following errors in the loadData() method:

adapter = new CategoriaAdapter((MainActivity) getActivity(), categoriaList);
listItem.setAdapter(adapter);
  • The adapter creation code must be defined before modifying the list (in onCreateView u onActivityCreated ). You are doing that for every time the list is modified, recreate a new adapter, which generates a lot of garbage for GC . Remember that when defining your list categoriaList should not be null and the code that should go in the task, is adapter.notifyDataSetChanged(); running in the main thread.

  • You are cleaning the list in an erroneous way (at the beginning of the method loadData() ). When really said if it should go within onComplete . The scenario can be given in which the list is deleted and the onComplete never returns result. So you would have the list of your adapter modified but never notified and could cause problems.

Solution: Move the if to the onComplete, it should stay something like this:

private void loadData() {
    isDataLoaded = true;
    miprogress.setVisibility(View.VISIBLE);
    db.collection("categoria")
        .get()
        .addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
              @Override
              public void onComplete(@NonNull Task<QuerySnapshot> task) {
                    if (categoriaList.size() > 0)
                        categoriaList.clear();

                   for (DocumentSnapshot doc : task.getResult()) {
                        CategoriaModel cat = new CategoriaModel(doc.getString("nombre"), doc.getString("url"), doc.getId());
                        categoriaList.add(cat);
                   }
                   adapter.notifyDataSetChanged();
                   miprogress.setVisibility(View.GONE);
             }
       })
       .addOnFailureListener(new OnFailureListener() {
              @Override
              public void onFailure(@NonNull Exception e) {
                   isDataLoaded = false;
                   Toast.makeText(getContext(), "" + e.getMessage(), Toast.LENGTH_SHORT).show();
                    }
        });
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    listItem = getActivity().findViewById(R.id.recycler_categorias);
    miprogress =  getView().findViewById(R.id.circularProgress);
    layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
    listItem.setHasFixedSize(true);
    listItem.setLayoutManager(layoutManager);
    adapter = new CategoriaAdapter((MainActivity) getActivity(), categoriaList);
    listItem.setAdapter(adapter);
}

@Override
public void onResume() {
    super.onResume();
    if(!isDataLoaded)
       loadData();

}
    
answered by 25.04.2018 / 23:23
source