1

I have a RecyclerView adapter with multiple ViewHolders. Each ViewHolder has a header TextView and a nested RecyclerView which was working fine. But I wanted to implement an expand/collapse function so that nested RecyclerView is hidden until the header is clicked. I used this method RecyclerView expand/collapse items. It works but when I click the header to expand the nested recylerview, the recyclerview doesn't populate any data. To be clear, it retrieves data but it's not visible. Any ideas why this might be?

This is my onBindViewMethod:

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

private Context mContext;
private List<EligibilityDetails> mEligsList;
private List<Items> mItemslist;
private LayoutInflater inflater;
private int mExpandedPosition = -1;

public EligibilityAdapter(Context context, List<EligibilityDetails> eligsList) {
    mContext = context;
    mEligsList = eligsList;
    inflater = LayoutInflater.from(context);
}

@Override
public int getItemViewType(int position) {
    switch (position) {
        case 0:
            return R.layout.rv_eligs_item_domestic;
        case 1:
            return R.layout.rv_eligs_item_overseas;
        default:
            return R.layout.rv_eligs_item_military;
    }
}

@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
    inflater = LayoutInflater.from(viewGroup.getContext());
    View view = inflater.inflate(i, viewGroup, false);
    ViewHolder holder = null;
    switch (i) {
        case R.layout.rv_eligs_item_domestic:
            holder = new DomesticViewHolder(view);
            break;
        case R.layout.rv_eligs_item_overseas:
            holder = new OverseasViewHolder(view);
            break;
        case R.layout.rv_eligs_item_military:
            holder = new MilitaryViewHolder(view);
            break;
    }
    return holder;
}

@Override
public int getItemCount() {
    return mEligsList.size();
}

public abstract class ViewHolder extends RecyclerView.ViewHolder {
    RecyclerView itemsRv;
    TextView mHeader;
    ItemsAdapter adapter;

    public ViewHolder(View itemView) {
        super(itemView);
        mHeader = (TextView) itemView.findViewById(R.id.header_tv);
        itemsRv = itemView.findViewById(R.id.recyclerViewItems);
    }
    public void setData(List<Items> list) {
        adapter.updateList(list);
    }
    abstract void bind(EligibilityDetails item);
}

public class DomesticViewHolder extends ViewHolder {

    TextView mHeader;
    RecyclerView itemsRv;
    ItemsAdapter adapter;

    public DomesticViewHolder(View itemView) {
        super(itemView);
        mHeader = (TextView) itemView.findViewById(R.id.header_tv);
        itemsRv = itemView.findViewById(R.id.recyclerViewItems);
    }
    public void setData(List<Items> list) {
        adapter.updateList(list);
    }
    @Override
    void bind(EligibilityDetails eligibilityDetails) {
        mHeader.setText(eligibilityDetails.getRequirementHeader());
        mItemslist = eligibilityDetails.getItemsList();
        ItemsAdapter itemsAdapter = new ItemsAdapter(mContext, mItemslist);
        itemsRv.setHasFixedSize(true);
        itemsRv.setLayoutManager(new CustomLinearLayoutManager(mContext));
        itemsRv.setAdapter(itemsAdapter);
        itemsRv.setNestedScrollingEnabled(false);
    }
}

public class OverseasViewHolder extends ViewHolder {

    TextView mHeader;
    RecyclerView itemsRv;
    ItemsAdapter adapter;

    public OverseasViewHolder(View itemView) {
        super(itemView);
        mHeader = (TextView) itemView.findViewById(R.id.header_tv);
        itemsRv = itemView.findViewById(R.id.recyclerViewItems);
    }
    public void setData(List<Items> list) {
        adapter.updateList(list);
    }

    @Override
    void bind(EligibilityDetails eligibilityDetails) {
        mHeader.setText(eligibilityDetails.getRequirementHeader());
        mItemslist = eligibilityDetails.getItemsList();
        ItemsAdapter itemsAdapter = new ItemsAdapter(mContext, mItemslist);
        itemsRv.setHasFixedSize(true);
        itemsRv.setLayoutManager(new CustomLinearLayoutManager(mContext));
        itemsRv.setAdapter(itemsAdapter);
        itemsRv.setNestedScrollingEnabled(false);
    }
}

public class MilitaryViewHolder extends ViewHolder {

    TextView mHeader;
    RecyclerView itemsRv;
    ItemsAdapter adapter;

    public MilitaryViewHolder(View itemView) {
        super(itemView);
        mHeader = (TextView) itemView.findViewById(R.id.header_tv);
        itemsRv = itemView.findViewById(R.id.recyclerViewItems);
    }
    public void setData(List<Items> list) {
        adapter.updateList(list);
    }

    @Override
    void bind(EligibilityDetails eligibilityDetails) {
        mHeader.setText(eligibilityDetails.getRequirementHeader());
        mItemslist = eligibilityDetails.getItemsList();
        final ItemsAdapter itemsAdapter = new ItemsAdapter(mContext, mItemslist);
        itemsRv.setHasFixedSize(true);
        itemsRv.setLayoutManager(new CustomLinearLayoutManager(mContext));
        itemsRv.setAdapter(itemsAdapter);
        itemsRv.setNestedScrollingEnabled(false);
    }
}

@Override
public void onBindViewHolder(@NonNull final EligibilityAdapter.ViewHolder viewHolder, int i) {
    viewHolder.bind(mEligsList.get(i));

    final boolean isExpanded = i == mExpandedPosition;
    viewHolder.itemsRv.setVisibility(isExpanded?View.VISIBLE:View.GONE);
    viewHolder.itemView.setActivated(isExpanded);
    viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mExpandedPosition = isExpanded ? -1:viewHolder.getAdapterPosition();
            ItemsAdapter itemsAdapter = new ItemsAdapter(mContext, mItemslist);
            viewHolder.itemsRv.setAdapter(itemsAdapter);
            //TransitionManager.beginDelayedTransition(recyclerView);
            notifyDataSetChanged();
        }
    });
}

where itemsRv is the nested RecyclerView. I've tried moving this logic to the individual viewholders and moving the recyclerview logic here like setting the adapter inside of the onClick method. Each time it comes up blank.

Thanks in advance.

Emily
  • 89
  • 1
  • 11

1 Answers1

1

Instead of having a new sub Recyclerview for every header you can create a multi view-type adapter that will have a view-type for your header and a view-type for your child-item.

And instead of using a just the header data-item for your list of data, use a generic type that will allow casting each data-type to its own view-type.

To do that we need to create an empty interface that all of our data-types will implement that way they are all generic.

public interface GenericDataType {}

so then your data-type will look like this

class HeaderItem implements GenericDataType {
    //All of your pojo data
    List<ChildrenItem> childrens; //ChildrenItem will also implement the GenericDataType that way both of the items are acceptable
}

So ones we did that we can replace the current items from

private List<EligibilityDetails> mEligsList;
private List<Items> mItemslist;

To

private List<GenericDataType> adapterItems;

Now we need to make sure that whenever we have a certain ViewHolder the item of that position will be of the right type. In order to do that we need to change our getItemViewType

@Override
public int getItemViewType(int position) {
    GenericItem currentItem = adapterItems.get(position);

    if (currentItem instanceOf HeaderItem) { //We have to use if else becasue java's switch does not supprt checking instancesOf
        return R.layout.your_header_item_layout_id;

    } else if(currentItem instanceOf ChildrenItem) {
        return R.layout.your_children_item_layout_id;

    } else if(repeat for any other types you have) {
        return R.layout.your_other_item_layout_id;

    } else {
        throw new Throwable("Unsupported type"); //This should never happen but we add it to make the compiler compile
    }
}

Now we can finally change our onBind method to support both types

@Override
public void onBindViewHolder(@NonNull final  EligibilityAdapter.ViewHolder viewHolder, int i) {

    if (viewHolder instanceOf HeaderViewHolder) {
        HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
        headerItem = (HeaderItem) items.get(i);
        //Do rest of binding here

        holder.viewToAddMoreItems.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!headerItem.isEpanded()) { //Add to your header type a Boolean to check if it is expanded or not 
                    adapterItems.addAll(headerItem.getChildrens()); //They are the same generic type but the getItemViewType will handle it for us
                    notifyItemRangeInserted(); //Provied item start index and size of the list
                } else {
                    adapterItems.removeAll(headerItem.getChildrens());
                    notifyItemRangeRemoved(); I don't remember what it require but you'll figure that out
                }
                headerItem.setExpanded(!headerItem.isExpanded()); //Flipping the value 
            }
        });

    } else if (viewHolder instanceOf ChildrenViewHolder) {
            ChildrenViewHolder holder = (ChildrenViewHolder) viewHolder;
            childrenItem = (ChildrenItem) items.get(i);
            //Do your binding here

    } else if(repeate for other view-types) {}
}
Gil Goldzweig
  • 1,809
  • 1
  • 13
  • 26
  • Thanks for taking the time. I tried to do something similar before and I couldn't make it compatible with a nested recyclerview. Could you edit the adapter file I posted above to include your idea? – Emily Nov 10 '18 at 11:35
  • Specifically, what is HeaderViewHolder? Is it supposed to be HeaderItem? – Emily Nov 10 '18 at 11:37
  • For example(It doesn't need to match but that's the idea), In your case, HeaderViewHolder will be equals to DomesticViewHolder and ChildrenViewHolder be OverseasViewHolder – Gil Goldzweig Nov 10 '18 at 11:39
  • If my answer helped you please approve and upvote it – Gil Goldzweig Nov 10 '18 at 13:42
  • I haven't been able to successfully implement this method. I'm working on it. – Emily Nov 10 '18 at 13:48
  • 1
    Oh ok, no problem, if you want to see an example, I have made a library that generates the RecyclerView adapter code at compile time, it features the same principle, there is a code shown what was generated and it should show you how you need to implement it, but it is in kotlin https://github.com/gilgoldzweig/Gencycler – Gil Goldzweig Nov 10 '18 at 13:52
  • Can you tell me what headerItem is in this line: headerItem = (HeaderItem) items.get(i); ? I assumed it was my POJO because it needs to access isExpanded() in the onClick method. But then why does it need cast to the header ViewHolder? – Emily Nov 10 '18 at 14:30
  • So the trick is you want to make the adapter generic that means for every layout you want to present you need to create a class that extends ViewHolder and a DataType(the object containing the data you want to populate on your layout) that is also generic. So in this case the HeaderItem is your parent pojo the one that contain the list and the ChildrenItem is the pojo from the list. It doesn't cast to the item it casts to the ViewHolder – Gil Goldzweig Nov 10 '18 at 14:36
  • @Emily Have a look at this question it has a link to the code. The folding concept could be applied with a little thought. This also has a Medium tutorial https://stackoverflow.com/questions/52747527/kotlin-recyclerview-data-not-showing-solved It lives here https://medium.com/@KotlinPUB/kotlinpub-series-nested-recyclerview-with-data-from-sqlite-db-13aad029d07b – Vector Nov 13 '18 at 15:39