androidlistviewfirebaselistview-adapter

ListView Custom Adapter - The Last Row Is Removed Instead of The Wanted One


I have a custom ListView adapter for displaying friend requests the user has received. For each row, there is an "accept" button. When the user clicks on that button, I replace the button with a progress bar while I update my database. After this process is done, I want to delete that row.

Well, this is almost what happens.

In order to show the progress bar I change the visibility of the button to be invisible, and the visibility of the progress bar to be visible.

The row is deleted properly and the database updates accordingly. The problem is that after I call the notifyDataSetChanged method, the only row which removed is the last one.

Here an updated version of my code snippet:

    public class FriendRequestsReceived_UserListAdapter extends GeneralListAdapter {

        private List<User> friendRequestsReceived_UserList;

        public FriendRequestsReceived_UserListAdapter(Context context, int resource, List<User> items) {
            super(context, resource, items);
            this.friendRequestsReceived_UserList=items;
        }

        private class ViewHolder {
            TextView userEmail_TextView;
            TextView name_TextView;
            Button acceptBtn;
            ProgressBar acceptProgressBar;
            User user;
        }

        @Override
        public View getView(int position, View view, ViewGroup parent) {
            ViewHolder holder;
            LayoutInflater inflater = LayoutInflater.from(getContext());

            if (view == null) {
                view = inflater.inflate(R.layout.friend_requests_received_listview_row, parent, false);

                holder = new ViewHolder();
                holder.userEmail_TextView = (TextView) view.findViewById(R.id.user_email);
                holder.name_TextView = (TextView) view.findViewById(R.id.user_name);
                holder.acceptBtn = (Button) view.findViewById(R.id.acceptRequestBtn);
                holder.acceptProgressBar = (ProgressBar) view.findViewById(R.id.acceptProgressBar);
                holder.user = (User) getItem(position);
                view.setTag(holder);
            } else
                holder = (ViewHolder) view.getTag();


            if (holder.user!=null) {
                holder.userEmail_TextView.setText(holder.user.getEmail());
                holder.name_TextView.setText(holder.user.getName());
                holder.acceptBtn.setTag(holder);
                holder.acceptBtn.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        acceptRequest(view);
                    }
                });
            }
            return view;
        }

        private void acceptRequest(View acceptButtonView){
            final ViewHolder vh = (ViewHolder) acceptButtonView.getTag();

            vh.acceptProgressBar.setVisibility(View.VISIBLE);
            vh.acceptBtn.setVisibility(View.INVISIBLE);

//Some code for updating database's related variables...

            mDatabase.updateChildren(dataForDatabase, new DatabaseReference.CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (databaseError != null) showConnectionErrorToast();
                    else {
                        Toast.makeText(getContext(), "Success", Toast.LENGTH_SHORT).show();
                        friendRequestsReceived_UserList.remove(vh.user);
                        notifyDataSetChanged();
                    }
                }
            });
        }
    }

I'm attaching a video of this activity in action that shows the problem: https://www.youtube.com/watch?v=9JoJ3RuRwsY


EDIT: The latest and working code:

public class FriendRequestsReceived_UserListAdapter extends GeneralListAdapter {

    private String loggedUser;
    private ConnectivityManager cm;
    private List<User> friendRequestsReceived_UserList;

    User working_user = null;

    public FriendRequestsReceived_UserListAdapter(Context context, int resource, List<User> items) {
        super(context, resource, items);
        this.friendRequestsReceived_UserList=items;
    }

    private class ViewHolder {
        Button acceptBtn;
        ProgressBar acceptProgressBar;
        User user;
    }

    @Override
    public View getView(int position, View view, ViewGroup parent) {
        LayoutInflater vi = LayoutInflater.from(getContext());

        view = vi.inflate(R.layout.friend_requests_received_listview_row, null);
        final ViewHolder holder = new ViewHolder();
        TextView userEmail_TextView = (TextView) view.findViewById(R.id.user_email);
        TextView name_TextView = (TextView) view.findViewById(R.id.user_name);
        Button acceptBtn = (Button) view.findViewById(R.id.acceptRequestBtn);
        ProgressBar acceptProgressBar = (ProgressBar) view.findViewById(R.id.acceptProgressBar);
        User myUser = (User) getItem(position);

        holder.acceptBtn=acceptBtn;
        holder.acceptProgressBar=acceptProgressBar;
        holder.user=myUser;

        acceptBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                acceptRequest(view, holder);
            }
        });


        userEmail_TextView.setText(myUser.getEmail());
        name_TextView.setText(myUser.getName());


        if (holder.user == working_user) {
            holder.acceptProgressBar.setVisibility(View.VISIBLE);
            holder.acceptBtn.setVisibility(View.INVISIBLE);
        }
        else {
            holder.acceptProgressBar.setVisibility(View.INVISIBLE);
            holder.acceptBtn.setVisibility(View.VISIBLE);
        }
        return view;
    }

    private void acceptRequest(View acceptButtonView, final ViewHolder holder){
        Context context=getContext();
        working_user = holder.user;

        holder.acceptProgressBar.setVisibility(View.VISIBLE);
        holder.acceptBtn.setVisibility(View.INVISIBLE);

        loggedUser=SaveSharedPreference.getLoggedEmail(context);
        final DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();

        final String senderEmail=holder.user.getEmail();

        cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        if (cm.getActiveNetworkInfo()!=null) {
            mDatabase.child("Users").child(loggedUser).addListenerForSingleValueEvent(new ValueEventListener() {
                @Override
                public void onDataChange(DataSnapshot dataSnapshot) {
                    if (dataSnapshot.exists()){
                        mDatabase.child("Users").child(senderEmail).addListenerForSingleValueEvent(new ValueEventListener() {
                            @Override
                            public void onDataChange(DataSnapshot dataSnapshot) {
                                if (dataSnapshot.exists()){

                                    Map data = new HashMap();
                                    data.put("Users/"+loggedUser+"/friend_requests_received/"+senderEmail, null);
                                    data.put("Users/"+loggedUser+"/friends_list/"+senderEmail,true);

                                    mDatabase.updateChildren(data, new DatabaseReference.CompletionListener() {
                                        @Override
                                        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                                            if (databaseError!=null)
                                                showConnectionErrorToast();

                                            else {//Success
                                                Context context=getContext();
                                                Toast.makeText(context,
                                                        "Success", Toast.LENGTH_SHORT).show();
                                                friendRequestsReceived_UserList.remove(holder.user);
                                                notifyDataSetChanged();

                                            }
                                        }
                                    });

                                }
                                else {
                                    showConnectionErrorToast();
                                }
                            }

                            @Override
                            public void onCancelled(DatabaseError databaseError) {
                                showConnectionErrorToast();
                            }
                        });
                    }
                    else {
                        showConnectionErrorToast();
                    }
                }

                @Override
                public void onCancelled(DatabaseError databaseError) {
                    showConnectionErrorToast();
                }
            });
        }
        else {
            showConnectionErrorToast();
        }
    }

Solution

  • Here's what I think happens (don't have access to android studio atm)

    Let's say you start with two users user1 , user2

    When the adapter initializes getCount returns 2;

    GetView runs twice, once for position 0 and once for 1

    This creates two views view1 which has viewholder vh1 who has user1 inside and view2->vh2->user2

    when you press accept you run your code and set view1's visibilities

    then you call notifydatasetchanged which invalidates the whole adapter

    the getcount method will return 1

    the getview method will initialize for position 0 but will start with the View argument initialized (view1)

    that argument has its view visibilities set to showing the progress bar and with a reference to user1

    what happens is that user1 is removed from the database but it still exists inside view1's viewholder, and view2 is disposed because it is no longer needed since getcount returned 1

    this is what I think should work:

    first you declare an instance variable:

    User _working_user = null;
    

    then

    @Override
    public View getView(int position, View view, ViewGroup parent) {
        ViewHolder holder;
        LayoutInflater inflater = LayoutInflater.from(getContext());
    
        if (view == null) {
            view = inflater.inflate(R.layout.friend_requests_received_listview_row, parent, false);
    
            holder = new ViewHolder();
            holder.userEmail_TextView = (TextView) view.findViewById(R.id.user_email);
            holder.name_TextView = (TextView) view.findViewById(R.id.user_name);
            holder.acceptBtn = (Button) view.findViewById(R.id.acceptRequestBtn);
            holder.acceptProgressBar = (ProgressBar) view.findViewById(R.id.acceptProgressBar);
    
            holder.acceptBtn.setOnClickListener(new View.OnClickListener() {
                 @Override
                 public void onClick(View view) {
                    acceptRequest(view);
                 }
             });
    
            view.setTag(holder);
        } else
            holder = (ViewHolder) view.getTag();
    
        holder.user = (User) getItem(position);
        holder.userEmail_TextView.setText(holder.user.getEmail());
        holder.name_TextView.setText(holder.user.getName());
        holder.acceptBtn.setTag(holder); //not a big fan of this btw, seems to be a cyclic reference
    
        if (holder.user == _working_user) {
            holder.acceptProgressBar.setVisibility(View.VISIBLE);
            holder.acceptBtn.setVisibility(View.INVISIBLE);
        } else {
            holder.acceptProgressBar.setVisibility(View.INVISIBLE);
            holder.acceptBtn.setVisibility(View.VISIBLE);
        }
    
    
        return view;
    }
    
    private void acceptRequest(View acceptButtonView){
            final ViewHolder vh = (ViewHolder) acceptButtonView.getTag();
    
            _working_user = vh.user; 
    
            vh.acceptProgressBar.setVisibility(View.VISIBLE);
            vh.acceptBtn.setVisibility(View.INVISIBLE);
            //thew above two lines could be replaced with a notifyDataSetChanged
    
    
            //Some code for updating database's related variables...
    
            mDatabase.updateChildren(dataForDatabase, new DatabaseReference.CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (databaseError != null) showConnectionErrorToast();
                    else {
                        Toast.makeText(getContext(), "Success", Toast.LENGTH_SHORT).show();
                        friendRequestsReceived_UserList.remove(vh.user);
                        notifyDataSetChanged();
                    }
                }
            });
        }
    

    now the above should work, but here's some notes

    We use the _working_user because there's a chance something else will call NotifyDataSetChanged() while the database stuff executing, if nothing does that , in theory it shouldn't be needed

    I don't really like the fact that the onclick handler passes the view, personal preference you may call it, what I would do is declare the click handler inside the if (view==null) block, and then call accept arguments with the view's holder's user object