0

All I want to do is increment the "like" value of the Comment object in my feed and notify the adapter that the item has changed. The expected behavior should be as follows: 1) User likes the Comment, 2) ListView updates with the new value for the Comment. Setting this.adapter.add(mComments.get(position - 1)); will replace it with the Comment before. Using this.adapter.add(mComments.get(position) crashes the app. Is there any way to do this? I guess since it's removed you can't really add it back... Besides upgrading to RecyclerAdapter and calling notifyItemChanged(int position), is there an alternative?

public class CommentAdapter extends ArrayAdapter<ParseObject> {

    protected Context mContext;
    protected List<ParseObject> mComments;
    private CommentAdapter adapter;

    ImageLoader profilePictureImageLoader;

    public CommentAdapter(Context context, List<ParseObject> comments) {
        super(context, R.layout.comment_listview_item, comments);
        mContext = context;
        mComments= comments;
        this.adapter = this;

        profilePictureImageLoader = new ImageLoader(new ProfilePictureFileCache(mContext));
    }

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

holder.likeImage.setOnClickListener(v -> {
    v.startAnimation(AnimationUtils.loadAnimation(getContext(), R.anim.image_click));
    createLike(position);
});

    return convertView;
}

Functions follow:

private void createLike(int position) {

        ParseQuery<ParseObject> query = new ParseQuery<>(ParseConstants.CLASS_COMMENT);
        query.whereEqualTo(ParseConstants.KEY_OBJECT_ID, getItem(position).getObjectId());
        query.findInBackground((comment, e) -> {
            // Find the single Comment object associated with the current ListAdapter position
            if (e == null) for (ParseObject commentObject : comment) {

                // Create a list to store the likers of this Comment
                List<String> likedBy = commentObject.getList("likedBy");
                System.out.println(likedBy);

                // If you are not on that list, then create a Like
                if (!(likedBy.contains(ParseUser.getCurrentUser().getObjectId()))) {

                    // Create new Like object
                    ParseObject newLike2 = new ParseObject(ParseConstants.CLASS_LIKE);
                    newLike2.put(ParseConstants.KEY_SENDER_ID, ParseUser.getCurrentUser());
                    newLike2.put(ParseConstants.KEY_COMMENT_OBJECT_ID, commentObject);
                    Toast.makeText(getContext(), "Comment liked", Toast.LENGTH_SHORT).show();
                    newLike2.saveInBackground();

                    // Add unique User objectId to likedBy array in Parse
                    commentObject.addAllUnique("likedBy", Collections.singletonList(ParseUser.getCurrentUser().getObjectId()));
                    commentObject.saveInBackground();

                    // Increment the likeCount in the Comment feed
                    incrementLikeCount(commentObject, position);

                    // Initiate Like notification
                    handleLikeNotification(query, commentObject);

                } else {
                    Toast.makeText(getContext(), "You already liked this Comment", Toast.LENGTH_SHORT).show();
                }

            }
            else {
                Log.e("Error", e.getMessage());
            }
        });

    }

private void incrementLikeCount(ParseObject commentObject, int position); 

    // Increment likeCount on related Comment object
    commentObject.increment("likeCount");
    commentObject.saveInBackground();

    this.adapter.remove(mComments.get(position));
    this.adapter.add(mComments.get(position));
    this.adapter.notifyDataSetChanged();

    }

I get the following exception:

08-28 22:46:04.359 28894-28894/com.yitter.android E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.yitter.android, PID: 28894
    java.lang.IndexOutOfBoundsException: Invalid index 3, size is 2
        at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:255)
        at java.util.ArrayList.get(ArrayList.java:308)
        at com.yitter.comment.CommentAdapter.lambda$incrementLikeCount$33(CommentAdapter.java:260)
        at com.yitter.comment.CommentAdapter.access$lambda$6(CommentAdapter.java:0)
        at com.yitter.comment.CommentAdapter$$Lambda$9.done(Unknown Source)
        at com.parse.ParseTaskUtils$2$1.run(ParseTaskUtils.java:116)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:148)
        at android.app.ActivityThread.main(ActivityThread.java:5417)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Martin Erlic
  • 5,467
  • 22
  • 81
  • 153
  • 1
    This is just a guess. I think that `mComments` is a reference to the same list used by the adapter. If that is the case, `this.adapter.remove(mComments.get(position))` will remove the element from `mComments` and reduce its size, hence the exception. – Titus Aug 28 '16 at 21:00
  • That assumption is probably correct. The item is no longer there so it can't be added back. Is there a way to avoid this in ListView so that the item can be changed instantly? – Martin Erlic Aug 28 '16 at 21:13
  • If that is the case, you don't need to add and remove the item, just update the adapter and the changes should be shown. – Titus Aug 28 '16 at 21:24
  • So, you want to update a row in the list view, right? (update the like count on a specific row) If so, have you tried this http://stackoverflow.com/a/3727813/1920068 ? It worked for me – Antonio Ribeiro Aug 28 '16 at 21:48

3 Answers3

1

Ok, I know how to solve your problem.

This is your piece of code:

private void incrementLikeCount(ParseObject commentObject, int position); 

    // Increment likeCount on related Comment object
    commentObject.increment("likeCount");
    commentObject.saveInBackground();

    this.adapter.remove(mComments.get(position));
    this.adapter.add(mComments.get(position));
    this.adapter.notifyDataSetChanged();

}

Look, if the list size is equals to 4, and you call incrementLikeCount with the value of position equals to 3 (the last element of the list), the app will crash. Y areou first removing an object of the list (so its size will now be 3 (and its last index position equals to 2), and after you are trying to retrieve the same element from the list passing the same position value (=3). As long as the last index is now equals to 2, and you are trying to getting the element that is in the index 3, the app will crash.

Consider changing the code to this:

private void incrementLikeCount(ParseObject commentObject, int position); 

    // Increment likeCount on related Comment object
    commentObject.increment("likeCount");
    commentObject.saveInBackground();

    this.adapter.remove(commentObject);
    this.adapter.add(commentObject);
    this.adapter.notifyDataSetChanged();

}

But look, you are removing and adding the same object to the list!!! What you could is just update the object and call this.adapter.notifyDataSetChanged();

private void incrementLikeCount(ParseObject commentObject, int position); 

    // Increment likeCount on related Comment object
    commentObject.increment("likeCount");
    commentObject.saveInBackground();

    this.adapter.notifyDataSetChanged();

}
Augusto Carmo
  • 4,386
  • 2
  • 28
  • 61
1

You don't need to add/remove views at all.

You should instead update your model (if you have a comment object for each row I assume, and with a "like" count) then call notifydatasetchanged().

Also you should probably update to using RecycleView, this is one of the reasons they created it, so you can just call notifyitemchanged() on one item instead of all of them.

breakline
  • 5,776
  • 8
  • 45
  • 84
0

Try this:

private void incrementLikeCount(ParseObject commentObject, int position); 
    commentObject.increment("likeCount");
    mComments.get(position).increment("likeCount");
    this.adapter.notifyDataSetChanged();      
    commentObject.saveInBackground();
}

Or, if the commentObject has more changes then the incremented likeCount, you can do this:

private void incrementLikeCount(ParseObject commentObject, int position); 
    commentObject.increment("likeCount");
    this.adapter.remove(position);
    this.adapter.add(commentObject);
    this.adapter.notifyDataSetChanged();      
    commentObject.saveInBackground();
}
Titus
  • 22,031
  • 1
  • 23
  • 33
  • This worked! Why ``notifyDataSetChanged`` before saving the object? – Martin Erlic Aug 28 '16 at 21:41
  • @bluemunch The local object `mComments.get(position)` was already modified, saving it will only update the cloud object, you don't need to wait for that in order to refresh the GUI. – Titus Aug 28 '16 at 21:44