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();
}
}
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