Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RecyclerView element update + async network call

Tags:

I have a recyclerview which works as expected. I have a button in the layout that fills the list. The button is supposed to make a async call, and on result, I change the button's look. All this happens fine.

But when I click on the button and scroll down the list fast, the async call's result updates the new view's button(the view that is in place of the old one). How do I handle this? Can I have a handle on when a particular view gets reused?

Update :

Code piece of the adapter class that does the async call and the updation of ui.

@Override public void onBindViewHolder(CommentsViewHolder holder, int position) {     try {          Comments comment = comments.get(position);         holder.bindView(comment,position);      }     catch(Exception ex){ex.printStackTrace();}  }  @Override public int getItemCount() {     if(comments==null)     {return 0;}     return comments.size();     //return comments.length(); }    public class CommentsViewHolder extends RecyclerView.ViewHolder {     TextView score ;      TextView commentText;     TextView commentTime;     TextView avatarId;     ImageButton minusOne;     ImageButton plusOne;     ParseObject model;      public CommentsViewHolder(View itemView) {         super(itemView);         //itemView.setBackgroundColor(Color.DKGRAY);         minusOne =(ImageButton)itemView.findViewById(R.id.decScore);         plusOne =(ImageButton)itemView.findViewById(R.id.incScore);         commentText = (TextView)itemView.findViewById(R.id.comment);         score = (TextView)itemView.findViewById(R.id.commentScore);         commentTime =(TextView)itemView.findViewById(R.id.commentTime);         avatarId = (TextView)itemView.findViewById(R.id.ivUserAvatar);     }     public void bindView(Comments comment, int position) {           commentText.setText(comment.getCommentText());          score.setText(Integer.toString(comment.getScore()));         String timeText = DateUtils.getRelativeTimeSpanString(  comment.getCreatedAt().getTime(), System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS).toString();         timeText = timeText.replace("hours","hrs");         timeText = timeText.replace("seconds","secs");         timeText = timeText.replace("minutes","mins");         commentTime.setText(timeText);         int commentHandler = comment.getCommenterHandle();         String commenterNumber = "";         if(commentHandler==0)         {             commenterNumber = "OP";         }         else{             commenterNumber = "#"+commentHandler;         }         avatarId.setText( commenterNumber);         model = comment;          String choice = "none";         minusOne.setEnabled(true);         plusOne.setEnabled(true);         minusOne.setVisibility(View.VISIBLE);         plusOne.setVisibility(View.VISIBLE);         for (ParseObject choiceIter : choices) {               if ((choiceIter.getParseObject("comment").getObjectId()).equals(comment.getObjectId())) {                 choice = choiceIter.getString("userChoice");                  break;             }         }           Log.i("debug",comment.getCommentText()+" "+comment.getScore()+" "+choice);          switch (choice) {              case "plusOne":                 Log.i("darkplus","setting darkplus");                 plusOne.setImageResource(R.drawable.ic_add_circle_black_18dp);                 plusOne.setOnClickListener(reversePlusOneOnClickListener);                 //minusOne.setOnClickListener(minusOneOnClickListener);                 minusOne.setVisibility(View.GONE);                 break;              case "minusOne":                 Log.i("darkminus","setting darkminus");                 minusOne.setImageResource(R.drawable.ic_remove_circle_black_18dp);                 minusOne.setOnClickListener(reverseMinusOneOnClickListener);                 //plusOne.setOnClickListener(plusOneOnClickListener);                 plusOne.setVisibility(View.GONE);                 break;              case "none":                 Log.i("darkregular","setting regular");                 minusOne.setImageResource(R.drawable.ic_remove_black_18dp);                 plusOne.setImageResource(R.drawable.ic_add_black_18dp);                  plusOne.setOnClickListener(plusOneOnClickListener);                 minusOne.setOnClickListener(minusOneOnClickListener);                 break;         }      }       View.OnClickListener reversePlusOneOnClickListener = new View.OnClickListener() {          @Override         public void onClick(View v) {              if (!FourUtils.isConnected(v.getContext())) {                 return;             }              minusOne.setEnabled(false);             plusOne.setEnabled(false);             model.increment("plusOne", -1);             model.increment("score", -1);              model.saveEventually(new SaveCallback() {                 @Override                 public void done(ParseException e) {                      if (e == null) {                         ParseQuery<ParseObject> query = ParseQuery.getQuery("CommentChoice");                         query.whereEqualTo("user", ParseUser.getCurrentUser());                         query.whereEqualTo("comment", model);                         query.fromPin(Four.COMMENT_CHOICE);                         query.getFirstInBackground(new GetCallback<ParseObject>() {                             @Override                             public void done(ParseObject parseObject, ParseException e) {                                 if (e == null) {                                     if (parseObject == null) {                                         parseObject = ParseObject.create("CommentChoice");                                         parseObject.put("comment", model);                                         parseObject.put("user", ParseUser.getCurrentUser());                                      }                                     parseObject.put("userChoice", "none");                                     parseObject.pinInBackground(Four.COMMENT_CHOICE, new SaveCallback() {                                         @Override                                         public void done(ParseException e) {                                             if (e == null) {                                                 score.setText(Integer.toString(model.getInt("score")));                                                 //votes.setText((model.getInt("minusOne") + model.getInt("plusOne")) + " votes");                                                  minusOne.setVisibility(View.VISIBLE);                                                 plusOne.setImageResource(R.drawable.ic_add_black_18dp);                                                 plusOne.setOnClickListener(plusOneOnClickListener);                                                 minusOne.setEnabled(true);                                                 plusOne.setEnabled(true);                                                // minusOne.setOnClickListener(minusOneOnClickListener);                                                 BusProvider.getInstance().post(new NewCommentChoicesAdded());                                             } else {                                                 e.printStackTrace();                                             }                                         }                                     });                                 }                                 else{e.printStackTrace();}                             }                         });                     } else {                         e.printStackTrace();                         Log.i("plus1 error", e.getMessage());                     }                  }             });         }     }; 
like image 794
55597 Avatar asked Jul 31 '15 14:07

55597


2 Answers

When the async code is done, you should update the data, not the views. After updating the data, tell the adapter that the data changed. The RecyclerView gets note of this and re-renders your view.
When working with recycling views (ListView or RecyclerView), you cannot know what item a view is representing. In your case, that view gets recycled before the async work is done and is assigned to a different item of your data.
So never modify the view. Always modify the data and notify the adapter. bindView should be the place where you treat these cases.

like image 171
Andrei Tudor Diaconu Avatar answered Sep 23 '22 19:09

Andrei Tudor Diaconu


Chet Haase from Google discusses your exact issue in this DevBytes video.

In short, the framework need to be notified that one of the Views is in "transient" state. Once notified, the framework will not recycle this View until its "transient" flag cleared.

In your case, before you execute the async action, call setHasTransientState(true) on the child View that should change when the async action completes. This View will not be recycled until you explicitly call setHasTransientState(false) on it.

Offtopic:

It looks like you might be manipulating UI elements from background threads. Don't do that! If you can have a reference to Activity then use its runOnUiThread(Runnable action) API instead. If getting a reference to Activity is difficult, you can obtain UI thread's Handler and use its post(Runnable action) API.

like image 43
Vasiliy Avatar answered Sep 23 '22 19:09

Vasiliy