How to have a ListView/RecyclerView inside a parent RecyclerView?

AndroidAndroid Recyclerview

Android Problem Overview


I want to add some child Views (List Items), which come to me from JSON formatted data. Each child list is under a parent list item row. How can I populate it in RecyclerView for each row item (Parent items with child list items)?

I have tried using RecyclerView within RecyclerView parent row (for populating child lists), but here child views are not visible.

Parent Adapter Class

public class DigitizedPrescAdapter extends RecyclerView.Adapter<DigitizedPrescAdapter.ListItemViewHolder>{
    private List<PrescriptionModal> prescriptionList;

    MedicinesInPrescAdapter adapter;

    public DigitizedPrescAdapter(List<PrescriptionModal> prescriptionListModal) {

        if (prescriptionListModal == null) {
            throw new IllegalArgumentException(
                    "PrescriptionList must not be null");
        }
        this.prescriptionList = prescriptionListModal;
    }

    @Override
    public ListItemViewHolder onCreateViewHolder(
            ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_row_digitised_request,
                        viewGroup,
                        false);
        return new ListItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(
            ListItemViewHolder viewHolder, int position) {
        PrescriptionModal model = prescriptionList.get(position);

        viewHolder.prescnum.setText("Prescription "+ ++position);
        viewHolder.prescNo.setText("Prescription: "+model.getPrescriptionID());
        viewHolder.doctorType.setText("Type: "+model.getDoctorType());
        viewHolder.doctorName.setText("Doctor: "+model.getDoctorName());
        viewHolder.patientName.setText("Patient: "+model.getPatientName());

        adapter = new MedicinesInPrescAdapter(model.getLstproduct());
        viewHolder.lstMedicines.setAdapter(adapter);

    }

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

    public final static class ListItemViewHolder
            extends RecyclerView.ViewHolder {

        TextView prescnum;
        TextView prescNo;
        TextView doctorType;
        TextView patientName;
        TextView doctorName;
        CheckBox selectAll;
        RecyclerView lstMedicines;

        public ListItemViewHolder(View itemView) {
            super(itemView);
            prescnum = (TextView) itemView.findViewById(R.id.prescnum);
            prescNo = (TextView) itemView.findViewById(R.id.prescNo);
            doctorType = (TextView) itemView.findViewById(R.id.doctorType);
            patientName = (TextView) itemView.findViewById(R.id.patientName);
            doctorName = (TextView) itemView.findViewById(R.id.doctorName);
            selectAll = (CheckBox) itemView.findViewById(R.id.selectAll);
            lstMedicines = (RecyclerView) itemView.findViewById(R.id.lstAllMedicines);
            MyLinearLayoutManager layoutManager = new MyLinearLayoutManager(itemView.getContext(),LinearLayoutManager.VERTICAL,false);
            lstMedicines.setHasFixedSize(false);
            lstMedicines.setLayoutManager(layoutManager);
        }
    }
}

Child Adapter Class

public class MedicinesInPrescAdapter extends RecyclerView.Adapter<MedicinesInPrescAdapter.MedicineListItemViewHolder>{

    List<Modal_Product_List> prescriptionProducts;

    public MedicinesInPrescAdapter(List<Modal_Product_List> prescriptionListProd) {

        if (prescriptionListProd == null) {
            throw new IllegalArgumentException(
                    "PrescriptionProductList must not be null");
        }
        this.prescriptionProducts = prescriptionListProd;
    }

    @Override
    public MedicineListItemViewHolder onCreateViewHolder(
            ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_row_medicine_productlist,
                        viewGroup,
                        false);
        return new MedicineListItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(
            MedicineListItemViewHolder viewHolder, int position) {
        Modal_Product_List modelMedicine = prescriptionProducts.get(position);

        viewHolder.medicineName.setText(modelMedicine.getMedicinename());
        viewHolder.medQty.setText(modelMedicine.getQuantity());
        viewHolder.days.setText("30");
        viewHolder.Amount.setText(modelMedicine.getQuantitybasedprice());
    }

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

    public final static class MedicineListItemViewHolder
            extends RecyclerView.ViewHolder {

        TextView medicineName;
        EditText medQty;
        TextView days;
        TextView Amount;
        CheckBox selectMe;

        public MedicineListItemViewHolder(View itemView) {
            super(itemView);
            medicineName = (TextView) itemView.findViewById(R.id.medicineName);
            medQty = (EditText) itemView.findViewById(R.id.medQty);
            days = (TextView) itemView.findViewById(R.id.days);
            Amount = (TextView) itemView.findViewById(R.id.amount);
            selectMe = (CheckBox) itemView.findViewById(R.id.selectMe);
        }
    }
}

Android Solutions


Solution 1 - Android

I got this issue few days ago and finally solved it. All you have to do is @override the layout manager onMeasure function as below:

CustomLinearLayoutManager

public class CustomLinearLayoutManager extends LinearLayoutManager {

    private static final String TAG = CustomLinearLayoutManager.class.getSimpleName();

    public CustomLinearLayoutManager(Context context) {
        super(context);
    }

    public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {

        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);
                

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {
        try {
            View view = recycler.getViewForPosition(0);//fix IndexOutOfBoundsException

            if (view != null) {
                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();

                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);

                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);

                view.measure(childWidthSpec, childHeightSpec);
                measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
                measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
                recycler.recycleView(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

You can just copy and paste the custom layout manager as above and in set up your recyclerview in the parent adapter like this:

RecyclerView.LayoutManager layoutManager = new CustomLinearLayoutManager(mContext);
holder.childRecyclerView.setLayoutManager(layoutManager);

Remember : Don't use same layoutManager as parent adapter, or error will occur.

Solution 2 - Android

By Android Support Library 23.2 of a support library version 23.2.0. So all WRAP_CONTENT should work correctly.

Please update version of a library in gradle file.

compile 'com.android.support:recyclerview-v7:23.2.0'

This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You’ll find all built in LayoutManagers now support auto-measurement.

you’ll be required to call setAutoMeasureEnabled(true)

> Below is the sample code

RecyclerView.LayoutManager layout = new LinearLayoutManager(context); 
layout.setAutoMeasureEnabled(true);

As setAutoMeasureEnabled is deprecated alternate solution This method was deprecated in API level 27.1.0. Implementors of LayoutManager should define whether or not it uses AutoMeasure by overriding isAutoMeasureEnabled().

Solution 3 - Android

If I understand, you want a RecyclerView with RecyclerView rows. In this case I recommend you to use an expandable RecyclerView, using this lib. Just follow the expandable example within the link.

There are many more cool features in the same lib, like Drag and Drop, Swipeable rows... Watch this less than a minute example video.

You just have to add the lib to the dependencies in your gradle.build file, like following:

dependencies {
    compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.7.4'
}

To be able to import the lib in your java files.

Solution 4 - Android

Please note: you should not create new Adapter each time when calls onBindView(), you should do that once in the onCreateView().

The Best Way - to use any library, for example - RendererRecyclerViewAdapter

How to add a NestedRecyclerView:

Step 1: Add the ViewModel interface to your Modal_Product_List

public class Modal_Product_List implements ViewModel {
    
    String getMedicinename() { ... } //your method
    int getQuantity() { ... } //your method
    int getQuantitybasedprice() { ... } //your method
}

Step 2: Create a ViewBinder for the Modal_Product_List:

private ViewRenderer getModalProductViewRenderer() {
	return new ViewBinder<>(
            R.layout.item_row_medicine_productlist, //your child layout id
            Modal_Product_List.class //your child item class
			(model, finder, payloads) -> finder
                .setText(R.id.medicineName, model.getMedicinename())
                .setText(R.id.medQty, (String) model.getQuantity())
                .setText(R.id.amount, (String) model.getQuantitybasedprice())
                .setChecked(R.id.selectMe, ...)
	);
}

Step 3: Add the CompositeViewModel interface to PrescriptionModal or extend from DefaultCompositeModel:

public class PrescriptionModal extends DefaultCompositeViewModel {

    String getPrescriptionID() {...} //your method
    String getDoctorType() {...} //your method
    String getDoctorName() {...} //your method
    String getPatientName() {...} //your method

    @Override
    List<Modal_Product_List> getItems() { return yourProductItems; }
}

Step 4: Create a ViewBinder for the PrescriptionModal:

private ViewRenderer getModalProductViewRenderer() {
    return new CompositeViewBinder<>(
		R.layout.item_row_digitised_request, //your parent item layout
		R.id.lstAllMedicines, //your nested RecyclerView
		PrescriptionModal.class, //your parent item class
        (model, finder, payloads) -> finder
            //no need to set child items, it will set automatically
            .setText(R.id.prescnum, "Prescription:" + model.getPrescriptionID)
            .setText(R.id.doctorName, "Doctor:" + model.getDoctorName())
            .setText(R.id.doctorType, "Type:" + model.getDoctorType())
            .setText(R.id.patientName, "Patient:" + model.getPatientName())
    ).registerRenderer(getModalProductViewRenderer()); //register ModalProductViewRenderer
     .registerRenderer(...) //if you need you can create other renderer and register here
);

Step 5(Optional): If you need a Custom LayoutManager, then extend the CompositeViewBinder and override the createLayoutManager method and use it instead of CompositeViewBinder

public class CustomCompositeViewBinder extends CompositeViewBinder {

    //...

    @Override
	protected RecyclerView.LayoutManager createLayoutManager() {
    	return new MyLinearLayoutManager(getContext(), VERTICAL, false);
    }
}

Step 6: Initialize RendererRecyclerViewAdapter and register renderer:

RendererRecyclerViewAdapter adapter = new RendererRecyclerViewAdapter(getContext());
recyclerView.setAdapter(adapter);

adapter.registerRenderer(getModalProductViewRenderer());
adapter.registerRenderer(...); //if you need you can create other renderers

adapter.setItems(getPrescriptionListModal());

It is really short and simple way to add a Nested RecyclerView

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRavi KabraView Question on Stackoverflow
Solution 1 - AndroidpptangView Answer on Stackoverflow
Solution 2 - AndroidMaheshwar LigadeView Answer on Stackoverflow
Solution 3 - AndroidGeraldo NetoView Answer on Stackoverflow
Solution 4 - AndroidVitalyView Answer on Stackoverflow