0

In my Listview this code works:

    for (int number = 0; number < matchingContacts.size(); number++) {

        //if a phone number is in our array of matching contacts
        if (matchingContacts.contains(selectPhoneContact.getPhone()))

        {
            //if a matching contact, no need to show the Invite button
            viewHolder.invite.setVisibility(View.GONE);

            //once a matching contact is found, no need to keep looping x number of time, move onto next contact
            break;

        } else {
            //if not a matching contact, no need to show the check box
            viewHolder.check.setVisibility(View.GONE);

        }

    }

If a phone number is in the matching arraylist then it should make the invite button invisible, if it is not in the matching arraylist it should make the checkbox invisible.

But not in my recyclerview, in which I am trying to make the code work.

On first load it looks ok but as soon as you start to scroll the views get messed up - checkboxes and buttons appear where they are not supposed to.

I've read that in Recyclerview you are supposed to implement this with case statements, and I've looked here Why RecyclerView items disappear with scrolling and here How to create RecyclerView with multiple view type? but for the life of me I cannot get it to work!

Can you help?

Here is my code:

public class PopulistoContactsAdapter extends RecyclerView.Adapter<PopulistoContactsAdapter.ViewHolder> {

    //make a List containing info about SelectPhoneContact objects
    public List<SelectPhoneContact> theContactsList;

    Context context_type;

    ArrayList<String> matchingContacts = new ArrayList<String>();

    public static class ViewHolder extends RecyclerView.ViewHolder {

        //In each recycler_blueprint show the items you want to have appearing
        public TextView title, phone;
        public CheckBox check;
        public Button invite;


        public ViewHolder(final View itemView) {
            super(itemView);
            //title is cast to the name id, in recycler_blueprint,
            //phone is cast to the id called no etc
            title = (TextView) itemView.findViewById(R.id.name);
            phone = (TextView) itemView.findViewById(R.id.no);
            invite = (Button) itemView.findViewById(R.id.btnInvite);
            check = (CheckBox) itemView.findViewById(R.id.checkBoxContact);

        }

    }

    public PopulistoContactsAdapter(List<SelectPhoneContact> selectPhoneContacts, Context context, int activity) {

        theContactsList = selectPhoneContacts;
        context_type = context;

        matchingContacts.add("+3531234567");
        matchingContacts.add("+3536789012");
        matchingContacts.add("+3530987654");
        matchingContacts.add("+3538765432");

    }

    @Override
    public PopulistoContactsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);
        View contactView = inflater.inflate(R.layout.recycler_blueprint, parent, false);
        ViewHolder viewHolder = new ViewHolder(contactView);

        return viewHolder;
    }

    @Override
    public void onBindViewHolder(final PopulistoContactsAdapter.ViewHolder viewHolder, final int position) {
        //bind the views into the ViewHolder
        //selectPhoneContact is an instance of the SelectPhoneContact class.
        //We will assign each row of the recyclerview to contain details of selectPhoneContact:

        //The number of rows will match the number of contacts in our contacts list
        final SelectPhoneContact selectPhoneContact = theContactsList.get(position);

        //a text view for the name, set it to the matching selectPhoneContact
        TextView title = viewHolder.title;
        title.setText(selectPhoneContact.getName());

        //a text view for the number, set it to the matching selectPhoneContact
        TextView phone = viewHolder.phone;
        phone.setText(selectPhoneContact.getPhone());

        Button invite = viewHolder.invite;

        CheckBox check = viewHolder.check;

        for (int number = 0; number < matchingContacts.size(); number++) {

            //if a phone number is in our array of matching contacts
            if (matchingContacts.contains(selectPhoneContact.getPhone()))

            {
                //if a matching contact, no need to show the Invite button
                viewHolder.invite.setVisibility(View.GONE);
                //once a matching contact is found, no need to keep looping x number of time, move onto next contact
                break;

            } else {
                //if not a matching contact, no need to show the check box
                viewHolder.check.setVisibility(View.GONE);

            }

        }

    }

    @Override
    public int getItemCount() {

        return theContactsList.size();
    }
}
CHarris
  • 2,693
  • 8
  • 45
  • 71
  • 1
    "how do I implement 2 possible viewholders?" -- you appear to have only one `ViewHolder` class in this code. See [this sample app](https://github.com/commonsguy/cw-omnibus/tree/v8.9/RecyclerView/HeaderList) for an example of using two different `ViewHolder` classes. "In my Listview this code works" -- not if that is the only code. In `getView()` of a `ListAdapter`, you need to update **every*** widget, because rows get recycled. In your case, you are hiding widgets, but never showing them. Your `RecyclerView.Adapter` suffers from this problem. – CommonsWare Jan 12 '18 at 23:14
  • I'll have a look at the sample app. I was trying to implement the two viewholders in my recyclerview but was getting so many errors I gave up. – CHarris Jan 12 '18 at 23:23

2 Answers2

1

RecyclerView can handle multiple view types with different view holders.

First of all you have to override the getItemViewType(int position) method on your adapter which will return type of the object according its position. Then create view holder class for each view type.

Handle onCreateViewHolder(ViewGroup parent, int viewType) method considering the view type:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == YOUR_FIRST_TYPE) {
        //inflate first type of view
        return new FirstTypeViewHolder(view);
    } else if (viewType == YOUR_SECOND_TYPE) {
        //inflate second type of view
        return new SecondTypeViewHolder(view);
    }
}

Handle onBindViewHolder(ViewHolder viewHolder, int position) considering the view type:

@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
    int viewType = getItemViewType(position);

    if (viewType == YOUR_FIRST_TYPE) {
        FirstTypeViewHolder firstTypeViewHolder = (FirstTypeViewHolder) viewHolder;
        //do your stuff
    } else if (viewType == YOUR_SECOND_TYPE) {
        SecondTypeViewHolder secondTypeViewHolder = (SecondTypeViewHolder) viewHolder;
        //do your stuff
    }
}

You can take a look on this tutorial.

Alex Kamenkov
  • 891
  • 1
  • 6
  • 16
  • Hey Alex, you should probably avoid posting link-only answers as links can be broken in the future. – Brian Jan 12 '18 at 23:16
1

Method 1: Actually in you case, using multiple View types is not necessary. This might be an easier way. Instead, I would recommend you amend your SelectPhoneContact class to include a simple boolean field (maybe called isMatching) that flags whether or not this phone number is a matching contact. You can then create a simple setter and getter methods like setIsMatchingContact(boolean) and isMatchingContact() to update/read this flag.

When you're initializing the list and adding the SelectPhoneContact, do the pre-processing of determining which instances belong or don't belong in the matching contacts by setting the setIsMatchingContact(boolean) method. In your onBindViewHolder method, rather than iterating over matchingContacts, you just check selectPhoneContact.isMatchingContact() and change the visibility accordingly. This is also more efficient as you don't have to perform a potentially expensive operation of iterating through a large list in a bind method which could cause the scrolling to stutter and have issues.

If your matchingContacts list changes throughout time, you could always write a method that iterates through the SelectPhoneContact list and resets the isMatching boolean.

Method 2: If you rather not extend the your SelectPhoneContact class to have those two methods I mentioned, you could alternatively create a private static wrapper class like this:

private static class SelectPhoneContactItem {
    SelectPhoneContact selectPhoneContact;
    boolean isMatching;
}

Then use this as the primary list in your adapter:

public List<SelectPhoneContactItem> theContactsList;

Like Method 1, you should the pre-processing of the figuring out which SelectPhoneContactItem is in the matchingContacts and assign the isMatching boolean as needed.

You should probably go with Method 1 unless there are some design constraints. Let me know if my answer makes sense, hope this helps!

Brian
  • 7,955
  • 16
  • 66
  • 107
  • Thanks for the answer. For method 1, getters and setters, do you mean `boolean isMatching; public boolean isMatching(){return isMatching;} public void setIsMatchingContact(boolean isMatching){ this.isMatching = isMatching; }`?That kind of thing? I'm useless with them. For method 2, preprocessing and figuring out, sure am I not going to have to use `for,if,else`, which will have to iterate through anyway? – CHarris Jan 13 '18 at 00:46
  • Your answer to Method 1 is correct. Method 2 really is the same thing as Method 1, it's just a small suggestion if you don't want to modify your `SelectPhoneContact` class (either because it's some external library class you can't modify or want to extend, or if you just want to keep your class code clean from view/controller logic). I shouldn't have really call them Method 1 and 2, sorry for the confusion. They're functionally the same approach. – Brian Jan 13 '18 at 05:15