I have a RecyclerView
which shows two kinds of View
s one represents an User publication and another that represents an Event publication. Both have elements in common, for example a TextView
that shows a time stamp. So I created a PublicationViewHolder
that takes this TextView
time stamp into a variable and load it. My issue is that the adapter, initially, load the right values, but when I scroll down, and scroll up again, the values in the positions are changed by values from another positions. Here is the code:
public class PublicationViewHolder extends RecyclerView.ViewHolder {
private TextView vTimeStamp;
public PublicationViewHolder(View itemView) {
super(itemView);
this.vTimeStamp = (TextView) itemView.findViewById(R.id.txt_view_publication_timestamp);
}
public void load(Publication publication, int i) {
load(publication);
try {
if (Publication.TYPE_USER_PUBLICATION == publication.getType()) {
load((UserPublication) publication);
} else if (Publication.TYPE_EVENT_PUBLICATION == publication.getType()) {
load((EventPublication) publication);
}
} catch (ClassCastException e) {
throw new RuntimeException("Publication type cast fail. See PublicationViewHolder.");
}
}
public void load(Publication publication) {
vTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
}
public void load( UserPublication publication) {
//This method is override by UserPublicationViewHolder
};
public void load( EventPublication publication) {
//This method is override by EventPublicationViewHolder
};
}
Now I will make my UserPublicationViewHolder
for users publications only.
public class UserPublicationViewHolder extends PublicationViewHolder {
private ImageView vImageView, vLikeButton, vDislikeButton, vFavButton, vEditPost, vDeletePost;
private TextView vText, vUsername, vLikeCount, vDislikeCount, vFavCount;
private PostImagesLayout vImagesContainer;
private TagCloudLocationFriends tagsView;
public UserPublicationViewHolder(View itemView) {
super(itemView);
vImageView = (ImageView) itemView.findViewById(R.id.img_view_publication_user);
vText = (TextView) itemView.findViewById(R.id.txt_view_publication_text);
vLikeCount = (TextView) itemView.findViewById(R.id.txt_view_like_count);
vFavCount = (TextView) itemView.findViewById(R.id.txt_view_fav_count);
vDislikeCount = (TextView) itemView.findViewById(R.id.txt_view_dislike_count);
vUsername = (TextView) itemView.findViewById(R.id.txt_view_publication_user_name);
vLikeButton = (ImageView) itemView.findViewById(R.id.img_view_like);
vDislikeButton = (ImageView) itemView.findViewById(R.id.img_view_dislike);
vFavButton = (ImageView) itemView.findViewById(R.id.img_view_fav);
vImagesContainer = (PostImagesLayout) itemView.findViewById(R.id.container_post_images);
tagsView = (TagCloudLocationFriends) itemView.findViewById(R.id.location_friends_tag);
// edit - remove icons
vDeletePost = (ImageView) itemView.findViewById(R.id.img_view_delete_post);
vEditPost = (ImageView) itemView.findViewById(R.id.img_view_edit_post);
}
@Override
public void load(UserPublication publication) {
//Load the UserPublicationViewHolder specific views.
}
}
Now I will do the same but for the Event publications
public class EventPublicationViewHolder extends PublicationViewHolder {
private TextView vTextViewTitle;
private TextView vTextViewText;
public EventPublicationViewHolder(View itemView) {
super(itemView);
vTextViewTitle = (TextView) itemView.findViewById(R.id.txt_view_publication_event_title);
vTextViewText = (TextView) itemView.findViewById(R.id.txt_view_publication_event_text);
}
@Override
public void load(EventPublication publication) {
//Load the EventPublicationViewHolder specifics views
}
}
Now here is my RecyclerView adapter:
public class PublicationAdapter extends RecyclerView.Adapter<PublicationViewHolder> {
public static final int USER_PUBLICATION_TYPE = 1;
public static final int EVENT_PUBLICATION_TYPE = 2;
private List<Publication> publications = new ArrayList<Publication>();
public List<Publication> getPublications() {
return publications;
}
public void setPublications(List<Publication> publications) {
this.publications = publications;
}
@Override
public int getItemViewType(int position) {
if (publications.get(position) instanceof UserPublication) {
return USER_PUBLICATION_TYPE;
}
if (publications.get(position) instanceof EventPublication) {
return EVENT_PUBLICATION_TYPE;
}
throw new RuntimeException("Unknown view type in PublicationAdapter");
}
@Override
public PublicationViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
View v;
switch (type) {
case USER_PUBLICATION_TYPE:
v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_user_publication, viewGroup, false);
return new UserPublicationViewHolder(v);
case EVENT_PUBLICATION_TYPE:
v = LayoutInflater.from(getActivity()).inflate(R.layout.view_holder_event_publication, viewGroup, false);
return new EventPublicationViewHolder(v);
}
return null;
}
@Override
public void onBindViewHolder(PublicationViewHolder aPublicationHolder, int i) {
aPublicationHolder.load(publications.get(i), i);
}
@Override
public long getItemId(int position) {
//Here I tried returning only position or 0 without luck.
//The id is unique BTW
return publications.get(position).getId();
}
@Override
public int getItemCount() {
return publications.size();
}
}
I don't know what can be wrong, UserPublication and EventPublication both extends from Publication. I'm not doing some request or reloading the adapter. I only load the adapter once.
Update:
BTW I'm using this RecyclerView inside a Fragment which is loaded in a PageAdapter which is loaded in a ViewPager that is inside a Fragment, maybe is this the issue?
Update: This is the other binding code.
This is the load method of the UserPublicationViewHolder
.
@Override
public void load(UserPublication publication) {
PicassoHelper.publicationUser(getActivity(), publication.getUser().getAvatarUrl(),
vImageView);
vText.setText(publication.getText());
vUsername.setText(publication.getUser().getName());
boolean hasLocation = false;
if (publication.getImages().length > 0) {
vImagesContainer.setImages(publication.getImages());
} else {
vImagesContainer.setVisibility(View.GONE);
}
tagsView.setTags(new ArrayList<MinikastTag>());
tagsView.drawTags();
if(publication.getLocation() != null || publication.getTaggedFriends().size() > 0){
if(publication.getLocation() != null){
hasLocation = true;
tagsView.add(new MinikastTag(1,"Post from ",1));
tagsView.add(new MinikastTag(2, publication.getLocation().getName(), 2));
}
if(publication.getTaggedFriends().size() > 0){
if(hasLocation)
tagsView.add(new MinikastTag(3," with ",1));
else
tagsView.add(new MinikastTag(3,"With ",1));
int i = 0;
for(User aUser: publication.getTaggedFriends()){
MinikastTag aTag;
if(i == publication.getTaggedFriends().size() - 1 ) {
aTag = new MinikastTag(4, aUser.getName(), 3);
aTag.setUserID(aUser.getId());
aTag.setUserName(aUser.getName());
tagsView.add(aTag);
} else {
aTag = new MinikastTag(4, aUser.getName() + ", ", 3);
aTag.setUserID(aUser.getId());
aTag.setUserName(aUser.getName());
tagsView.add(aTag);
}
i = i+1;
}
}
}
tagsView.drawTags();
// likes, dislikes, favs
if(publication.getLikesAmount() > 0)
vLikeCount.setText(String.valueOf(publication.getLikesAmount()));
if(publication.getDislikesAmount() > 0)
vDislikeCount.setText(String.valueOf(publication.getDislikesAmount()));
if(publication.getLovesAmount() > 0)
vFavCount.setText(String.valueOf(publication.getLovesAmount()));
// reset buttons
vFavButton.setPressed(false);
vDislikeButton.setPressed(false);
vLikeButton.setPressed(false);
if(publication.getRelationship().equals("LOVE"))
vFavButton.setPressed(true);
else if (publication.getRelationship().equals("LIKE"))
vLikeButton.setPressed(true);
else if (publication.getRelationship().equals("DISLIKE"))
vDislikeButton.setPressed(true);
// edit - remove icons
if(String.valueOf(publication.getUser().getId()).equals(StartupSharedPreferences.getProfileId())){
vEditPost.setVisibility(View.VISIBLE);
vDeletePost.setVisibility(View.VISIBLE);
}else{
vEditPost.setVisibility(View.INVISIBLE);
vDeletePost.setVisibility(View.INVISIBLE);
}
}
}
And this is the load method of the EventPublicationViewHolder:
@Override
public void load(EventPublication publication) {
vTimeStamp.setVisibility(View.GONE);
itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//GoTo.eventDetail(getActivity(), publication);
}
});
vTextViewTitle.setText(publication.getTitle());
vTextViewText.setText(publication.getText());
}
I commented some code just because I was testing, but as you can see, I only do setTexts and assing some images.
And this is how I set the adapter, LinearLayoutManager, etc. In the onViewCreated method of the fragment.
vRecyclerView = (FixedRecyclerView) view.findViewById(R.id.recycler_view_publications);
vSwipeRefresh = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container);
mFeedCallback.onScrollReady(vRecyclerView);
mLayoutManager = buildLayoutManager();
vRecyclerView.setLayoutManager(mLayoutManager);
vRecyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
mAdapter = new PublicationAdapter();
vSwipeRefresh.setOnRefreshListener(this);
vSwipeRefresh.setColorSchemeResources(R.color._SWIPER_COLOR_1, R.color._SWIPER_COLOR_2,
R.color._SWIPER_COLOR_3, R.color._SWIPER_COLOR_4);
vRecyclerView.setAdapter(mAdapter);
BTW The adapter is loaded with the dataset in a custom method that I have, called onHttpClientReady, but this doesn't seems to be the issue.
Here are some screenshots:
Top of the list when I enter in the app for first time:
Then when I come back:
BTW the like, dislike and favorite buttons, if someone did click them more than once, will display a numerical value, this values also are misplaced if they are.
UPDATE: Now I know that wasn't because the nested fragments. I changed my code in the way that, now, each tab fragment is in the PageStateAdapter that is inside the ViewPager that is inside an Activity. But the issue is still there.
UPDATE: I found that the getItemId method is never being executed, IDK why yet.
This usually happens when you have something like "if (field != null) holder.setField(field)", without an else. The holder is recycled, this means that it will have values there, so you need to clean or replace EVERY value, if it's null you should nullit, if it's not, you should write it, ALWAYS. It's late, but, as an answer for others.
for me setting setHasStableIds(false)
solved the problem.
I'd suggest reviewing your class hierarchy and usage. In general, if you are doing a type == type
kind of operation in a base class then you are defeating the purpose of the abstraction and inheritance. Something like this would work for you:
public abstract class PublicationViewHolder extends RecyclerView.ViewHolder {
private TextView mTimeStamp;
public PublicationViewHolder(View itemView) {
mTimeStamp = (TextView)itemView.findViewById(R.id. txt_view_publication_timestamp);
}
public void bindViews(Publication publication) {
mTimeStamp.setText(DateFormatter.getTimeAgo(publication.getTimeStamp()));
}
}
Now your "event" or "user publications" simply derive from this class and implement the constructor and bindViews()
method. Be sure to call through to the superclass in both cases. Also, be sure that you set every view in the layout for the specific publication in your bindViews()
methods.
In your adapter, you just need to create the correct holder based on the publication type at that position in your data set:
public class PublicationAdapter extends RecyclerView.Adapter {
private ArrayList<Publication> mPubs;
// Your other code here, like
// swapPublications(), getItemCount(), etc.
...
public int getItemViewType(int position) {
return mPubs.get(position).getType();
}
public PublicationViewHolder createViewHolder(ViewGroup parent, int type) {
PublicationViewHolder ret;
View root;
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
if (type == USER_PUBLICATION_TYPE) {
root =
inflater.inflate(R.layout.view_holder_user_publication,
parent,
false);
ret = new UserPubHolder(root);
} else {
root =
inflater.inflate(R.layout.view_holder_event_publication,
parent,
false);
ret = new EventPubHolder(root);
}
return ret;
}
public bindViewHolder(PublicationViewHolder holder, int position) {
holder.bindViews(mPubs.get(position));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With