javaandroidfirebase-realtime-databaseandroid-recyclerviewandroid-adapter

Why is my RecyclerView showing the list duplicated?


I have an Activity with a RecyclerView that gets a list of objects from my Fireabase Database. Until this point everything works as expected. On the adapter I have a button do delete one object. This button deletes the object from both the list and from the database.

When click on it, the object is indeed deleted from both places. The problem that it duplicates the list on the RecyclerView.

How can I fix this behavior?

RecyclerView

public class ApiariosListActivity extends AppCompatActivity implements ApiarioOnListAdapter.ApiarioClickListener {

    private ApiarioOnListAdapter apiarioOnListAdapter;

    private ArrayList<Apiario> apiList;
    private static final int EDIT_APIARIO_REQUEST_CODE = 1;
    private static final int ADD_APIARIO_REQUEST_CODE = 2;

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

        RecyclerView recyclerView = findViewById(R.id.recyclerViewApiarios);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        this.apiList = new ArrayList<>();

        DatabaseReference mDatabaseQuery = FirebaseDatabase.getInstance().getReference("apiario");

        Query query = mDatabaseQuery;

        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                for(DataSnapshot dataSnapshot : snapshot.getChildren()){

                    Apiario apiario = dataSnapshot.getValue(Apiario.class);
                    apiList.add(apiario);
                }

                apiarioOnListAdapter = new ApiarioOnListAdapter(apiList, ApiariosListActivity.this);
                recyclerView.setAdapter(apiarioOnListAdapter);
            }

            @Override
            public void onCancelled(@NonNull DatabaseError error) {

            }
        });


        // Add Apiario button
        ImageButton btnAddApiario = findViewById(R.id.btnAddApiario);
        btnAddApiario.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(ApiariosListActivity.this, SelectDistrictActivity.class);
                startActivityForResult(intent, ADD_APIARIO_REQUEST_CODE);
            }
        });
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        Log.d("AnualDeclarationActivity", "JV: onRestart");
    }

    @Override
    protected void onStart() {
        super.onStart();
        Log.d("AnualDeclarationActivity", "JV: onStart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("AnualDeclarationActivity", "JV: onResume");
    }

    @Override
    public void onEditClick(Apiario apiario) {
        Log.d("AnualDeclarationActivity", "JV: onEditClick");
        Intent intent = new Intent(this, EditApiarioActivity.class);
        intent.putExtra("apiario", apiario);
        startActivityForResult(intent, EDIT_APIARIO_REQUEST_CODE);
    }

    @Override
    public void onDeleteClick(Apiario apiario) {
        // Implement delete functionality, e.g., show a confirmation dialog
        showDeleteConfirmationDialog(apiario);
    }

    private void showDeleteConfirmationDialog(final Apiario apiario) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Delete Apiario");
        builder.setMessage("Are you sure you want to delete this Apiario?");

        builder.setPositiveButton("Delete", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                // Perform the delete operation
                deleteApiario(apiario);
            }
        });

        builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialogInterface, int i) {
                // Cancel the delete operation
                dialogInterface.dismiss();
            }
        });

        builder.create().show();
    }

    private void deleteApiario(Apiario apiario) {
        // Remove the apiario from the list
        apiList.remove(apiario);
        // Notify the adapter of the change
        apiarioOnListAdapter.notifyDataSetChanged();
        // Inform the user
        Toast.makeText(this, "Apiario deleted: " + apiario.getNomeApiario(), Toast.LENGTH_SHORT).show();
    }

    private class PostDataTask extends AsyncTask<List<Apiario>, Void, String> {

        @Override
        protected void onPreExecute() {
            Log.d("AsyncTask", "Executing onPreExecute");
        }

        @Override
        protected String doInBackground(List<Apiario>... apiariosList) {
            Log.d("AsyncTask", "Execute doInBackground");

            // Create a JSON array to hold multiple Apiario objects
            JSONArray apiarioArray = new JSONArray();

            // Convert each Apiario object to a JSON object and add it to the array
            for (Apiario apiario : apiariosList[0]) {
                try {
                    JSONObject apiarioObject = new JSONObject();
                    apiarioObject.put("nomeApiario", apiario.getNomeApiario());
                    apiarioObject.put("apiarioDeTransumancia", apiario.isApiarioDeTransumancia()); // Example value, modify as needed
                    apiarioObject.put("numColmeias", apiario.getNumColmeias());
                    apiarioObject.put("numNucleos", apiario.getNumNucleos());
                    apiarioObject.put("coordenadaX", apiario.getCoordenadaX());
                    apiarioObject.put("coordenadaY", apiario.getCoordenadaY());
                    apiarioObject.put("freguesia", apiario.getFreguesia());

                    apiarioArray.put(apiarioObject);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }

            // Create the main JSON object with the array
            JSONObject mainJsonObject = new JSONObject();
            try {
                mainJsonObject.put("declarationModel", apiarioArray);
            } catch (JSONException e) {
                e.printStackTrace();
            }

            ApiCaller apiCaller = new ApiCaller();
            String apiUrl = "http://10.0.2.2:5176/api/DeclarationController/SubmitAnualDeclaration";
            String jsonInputString = mainJsonObject.toString();

            Log.d("AsyncTask", "doInBackground: Making install request with JSON: " + jsonInputString);

            String result = apiCaller.makePost(apiUrl, jsonInputString);

            if (result != null) {
                Log.d("AsyncTask", "doInBackground: Submission successful. Response: " + result);
                return result;
            } else {
                Log.d("AsyncTask", "doInBackground: Submission request failed.");
                return null;
            }
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d("AsyncTask", "FP : Executing onPostExecute");
            if (result != null) {
                Toast.makeText(ApiariosListActivity.this, "Success", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(ApiariosListActivity.this, "Error", Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d("TEST", "JV: onActivityResult - requestCode: " + requestCode + ", resultCode: " + resultCode);
        Log.d("TEST", "JV: ON ACTIVITY");

        if (requestCode == ADD_APIARIO_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
            // Retrieve the newly added Apiario object
            Apiario newApiario = data.getParcelableExtra("newApiario");

            // Add the new Apiario to the list
            apiList.add(newApiario);

            // Notify the adapter of the change
            apiarioOnListAdapter.notifyDataSetChanged();
        }

        if (requestCode == EDIT_APIARIO_REQUEST_CODE && resultCode == RESULT_OK && data != null) {
            Apiario modifiedApiario = data.getParcelableExtra("modifiedApiario");
            int position = findPositionOfApiarioInList(modifiedApiario);
            if (position != -1) {
                apiList.set(position, modifiedApiario);
                apiarioOnListAdapter.notifyItemChanged(position);
            }
        }
    }

    private int findPositionOfApiarioInList(Apiario apiario) {
        for (int i = 0; i < apiList.size(); i++) {
            if (apiList.get(i).getNomeApiario().equals(apiario.getNomeApiario())) {
                return i;
            }
        }
        return -1;
    }

}

Adapter

public class ApiarioOnListAdapter extends RecyclerView.Adapter<ApiarioOnListAdapter.ApiarioViewHolder> {

    private List<Apiario> apiarios;
    private ApiarioClickListener listener;

    public interface ApiarioClickListener {
        void onEditClick(Apiario apiario);
        void onDeleteClick(Apiario apiario);
    }

    public ApiarioOnListAdapter(List<Apiario> apiarios, ApiarioClickListener listener) {
        this.apiarios = apiarios;
        this.listener = listener;
    }

    public class ApiarioViewHolder extends RecyclerView.ViewHolder {
        TextView textName;
        TextView textEstado;

        Button btnEdit;
        Button btnDelete;
        LinearLayout buttonContainer; // Add this line

        public ApiarioViewHolder(@NonNull View itemView) {
            super(itemView);
            textName = itemView.findViewById(R.id.textName);
            textEstado = itemView.findViewById(R.id.textEstado);

            btnEdit = itemView.findViewById(R.id.btnEdit);
            btnDelete = itemView.findViewById(R.id.btnDelete);
            buttonContainer = itemView.findViewById(R.id.buttonContainer);
        }
    }

    @NonNull
    @Override
    public ApiarioViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.apiario_item_list, parent, false);
        return new ApiarioViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ApiarioViewHolder holder, int position) {
        final Apiario apiario = apiarios.get(position);

        holder.textName.setText("Nome: " + apiario.getNomeApiario());
        holder.textEstado.setText("Estado: " + apiario.getEstado());

        holder.btnEdit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Apiario selectedApiario = apiarios.get(holder.getAdapterPosition());
                Log.d("TEST", "JV: SELECTED " + selectedApiario.getNomeApiario());
                listener.onEditClick(selectedApiario);
            }
        });

        holder.btnDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //listener.onDeleteClick(apiario);
                int apiarioId = apiario.getID();
                Log.d("TEST", "JV: ID A REMOVER " + apiarioId);

                DatabaseReference apiarioRef = FirebaseDatabase.getInstance().getReference("apiario");

                Query query = apiarioRef.orderByChild("id").equalTo(apiarioId);

                query.addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull DataSnapshot snapshot) {
                        for (DataSnapshot data : snapshot.getChildren()) {
                            String apiarioId = data.getKey();
                            Log.d("TEST", "JV: ENTRA NO FOR DA REMOÇAO " + apiarioId);
                            // Obtém a referência do banco de dados para o nó 'apiario' usando o ID do apiário
                            DatabaseReference apiarioToRemoveRef = apiarioRef.child(apiarioId);

                            // Remove o nó 'apiario' da base de dados
                            apiarioToRemoveRef.removeValue();

                            // Opcional: Remover o APIário da lista local
                            apiarios.remove(apiario);

                            // Notificar o adapter sobre a mudança nos dados
                            notifyDataSetChanged();


                        }
                    }

                    @Override
                    public void onCancelled(@NonNull DatabaseError error) {

                    }
                });
            }
        });

        // Check if the state is "Pendente" and add a button dynamically
        if (apiario.getEstado().equalsIgnoreCase("Pendente")) {
            Button customButton = new Button(holder.itemView.getContext());
            customButton.setText("Confirmar");
            // Add the button to the layout
            holder.buttonContainer.addView(customButton);
            // Set a click listener for the custom button
            customButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    // Handle button click
                    // You can perform specific actions based on the button click
                    DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference("apiario");
                    Query query = mDatabase.orderByChild("id").equalTo(apiario.getID());

                    query.addListenerForSingleValueEvent(new ValueEventListener() {
                        @Override
                        public void onDataChange(@NonNull DataSnapshot snapshot) {

                            for (DataSnapshot data : snapshot.getChildren()){

                                String confirmID = data.getKey();
                                mDatabase.child(confirmID).child("estado").setValue("Aceite");
                                holder.buttonContainer.removeAllViews();

                                // Atualizar o estado na lista local
                                apiario.setEstado("Aceite");

                                // Notificar o adapter sobre a mudança nos dados
                                notifyDataSetChanged();

                            }


                        }

                        @Override
                        public void onCancelled(@NonNull DatabaseError error) {

                        }
                    });


                }
            });
        } else {
            // If the state is not "Pendente", make sure to remove any previously added button
            holder.buttonContainer.removeAllViews();
        }
    }

    private void openEditApiarioActivity(Context context, Apiario apiario) {
        Intent intent = new Intent(context, EditApiarioActivity.class);
        intent.putExtra("apiario", apiario);
        context.startActivity(intent);
    }

    @Override
    public int getItemCount() {
        return apiarios.size();
    }
}

Solution

  • The problem is here:

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_apiarios_list);
    
        RecyclerView recyclerView = findViewById(R.id.recyclerViewApiarios);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    
        this.apiList = new ArrayList<>(); // 👈
    
        DatabaseReference mDatabaseQuery = FirebaseDatabase.getInstance().getReference("apiario");
    
        Query query = mDatabaseQuery;
    
        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                for(DataSnapshot dataSnapshot : snapshot.getChildren()){
                    Apiario apiario = dataSnapshot.getValue(Apiario.class);
                    apiList.add(apiario); // 👈
                }
    
                apiarioOnListAdapter = new ApiarioOnListAdapter(apiList, ApiariosListActivity.this);
                recyclerView.setAdapter(apiarioOnListAdapter);
            }
    

    Since you use addValueEventListener your onDataChange will be called "immediately" with the initial data and then again whenever the data at query changes. But you only create a new, empty apiList once (in onCreate), so each subsequent time that onDataChange gets called, you're adding items to the already populated list.

    A better implementation would be:

    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_apiarios_list);
    
        RecyclerView recyclerView = findViewById(R.id.recyclerViewApiarios);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
    
        this.apiList = new ArrayList<>();
    
        DatabaseReference mDatabaseQuery = FirebaseDatabase.getInstance().getReference("apiario");
    
        Query query = mDatabaseQuery;
    
        query.addValueEventListener(new ValueEventListener() {
            @Override
            public void onDataChange(@NonNull DataSnapshot snapshot) {
                apiList.clear(); // 👈 Clear previous results
                for(DataSnapshot dataSnapshot : snapshot.getChildren()){
    
                    Apiario apiario = dataSnapshot.getValue(Apiario.class);
                    apiList.add(apiario);
                }
    
                apiarioOnListAdapter = new ApiarioOnListAdapter(apiList, ApiariosListActivity.this);
                recyclerView.setAdapter(apiarioOnListAdapter);
            }
    
            @Override
            public void onCancelled(@NonNull DatabaseError error) {
                throw error.toException(); // 👈 Never ignore errors
            }
        });
    

    If you don't need apiList outside of onDataChange (something I'd recommend), you could/should probably move it to be a local variable in onDataChange. That would've avoided this problem, and will also prevent problems related to trying to access data before it is loaded/updated.