Florian Walther
Florian Walther

Reputation: 6971

Firebase ValueEventListener causing duplicate entries in the RecyclerView

Since the ValueEventListener gets triggered every time the data in the Firebase Database updates, it is also called when a new upload is done while the RecyclerView is showing. The way I have it now causes duplicate entries when this happens, because the Upload objects are already in the mUploads List. And when an upload is done, the whole database gets queried again and all items added to the already existing ArrayList I don't know how to solve this. Should I create a new Arraylist every time the ValueEventListener is triggered or do I need a completely different callback? Also the ValueEventListener interfers with my mAdapter.notifyItemRemoved call because the ValueEventListener gets triggered as soon as I delete something.

public class ImagesActivity extends AppCompatActivity implements ImageAdapter.OnItemClickListener {
private RecyclerView mRecyclerView;
private ImageAdapter mAdapter;

private ProgressBar mProgressCircle;

private DatabaseReference mDatabaseRef;
private FirebaseStorage mStorage;

private List<Upload> mUploads;

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

    mUploads = new ArrayList<>();

    mRecyclerView = findViewById(R.id.recycler_view);
    mRecyclerView.setHasFixedSize(true);
    mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

    mProgressCircle = findViewById(R.id.progress_circle);

    mDatabaseRef = FirebaseDatabase.getInstance().getReference("uploads");
    mStorage = FirebaseStorage.getInstance();

    mDatabaseRef.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {

            for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
                Upload upload = postSnapshot.getValue(Upload.class);
                upload.setKey(postSnapshot.getKey());

                mUploads.add(upload);
            }

            mAdapter = new ImageAdapter(ImagesActivity.this, mUploads);

            mRecyclerView.setAdapter(mAdapter);

            mAdapter.setOnItemClickListener(ImagesActivity.this);

            mProgressCircle.setVisibility(View.INVISIBLE);
        }

        @Override
        public void onCancelled(DatabaseError databaseError) {
            Toast.makeText(ImagesActivity.this, databaseError.getMessage(), Toast.LENGTH_LONG).show();
            mProgressCircle.setVisibility(View.INVISIBLE);
        }
    });
}

@Override
public void onDeleteClick(final int position) {
    Upload selectedItem = mUploads.get(position);
    final String selectedKey = selectedItem.getKey();

    StorageReference imageRef = mStorage.getReferenceFromUrl(selectedItem.getImageUrl());
    imageRef.delete().addOnSuccessListener(new OnSuccessListener<Void>() {
        @Override
        public void onSuccess(Void aVoid) {
            mDatabaseRef.child(selectedKey).removeValue();
            mUploads.remove(position);
            mAdapter.notifyItemRemoved(position);
        }
    });

}
}

Upvotes: 3

Views: 5780

Answers (5)

Frank van Puffelen
Frank van Puffelen

Reputation: 599571

Instead of using a ValueEventListener consider attaching a ChildEventListener. This type of listener gets triggered for the individual child nodes that are added, changed, or removed.

When you initially attach a ChildEventListener its onChildAdded will be called for each child that the listener matches. Then when you add a child node to the database later, the onChildAdded will be called again with only the existing child. This makes it super easy to update the UI, just add the new child to the adapter.

A ChildEventListener also has an onChildChanged event, which gets triggered when a specific child is changed. Here too, the method gets triggered with only the existing child, once again making it easy to update just that child in the adapter.

See listen for child events in the Firebase documentation for more on this type of listener.

Upvotes: 3

Omkar
Omkar

Reputation: 3100

clear mUploads before adding data in addValueEventListener like below

 mDatabaseRef.addValueEventListener(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            mUploads.clear(); //change here

            for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
                Upload upload = postSnapshot.getValue(Upload.class);
                upload.setKey(postSnapshot.getKey());

                mUploads.add(upload);
            }

            mAdapter = new ImageAdapter(ImagesActivity.this, mUploads);

            mRecyclerView.setAdapter(mAdapter);

            mAdapter.setOnItemClickListener(ImagesActivity.this);

            mProgressCircle.setVisibility(View.INVISIBLE);
        }

Upvotes: 5

Ali Faris
Ali Faris

Reputation: 18610

I suggest that you use addListenerForSingleValueEvent so that the data will be fetched once and at the end add addChildEventListener and override onChildAdded so new add items will be fetched also , I think this better than clearing the list and re-inflating the recyclerview

checkout the code

mDatabaseRef.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {

        for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
            Upload upload = postSnapshot.getValue(Upload.class);
            upload.setKey(postSnapshot.getKey());

            mUploads.add(upload);
        }

        mAdapter = new ImageAdapter(ImagesActivity.this, mUploads);
        mRecyclerView.setAdapter(mAdapter);
        mAdapter.setOnItemClickListener(ImagesActivity.this);
        mProgressCircle.setVisibility(View.INVISIBLE);

        mDatabaseRef.addChildEventListener(new ChildEventListener(){
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
                Upload upload = postSnapshot.getValue(Upload.class);
                upload.setKey(postSnapshot.getKey());
                mUploads.add(upload);
            }

        })
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Toast.makeText(ImagesActivity.this, databaseError.getMessage(), Toast.LENGTH_LONG).show();
        mProgressCircle.setVisibility(View.INVISIBLE);
    }
});

Upvotes: 1

Pavel Poley
Pavel Poley

Reputation: 5597

You can use addListenerForSingleValueEvent() and this will read the data once, if you want to refresh the data, you can call it again, for more info Firebase - read data once

Upvotes: 1

Ratilal Chopda
Ratilal Chopda

Reputation: 4220

Try this

clear arraylist mUploads.clear() before addValueEventListener like below

mUploads.clear();

Upvotes: 2

Related Questions