How to add a recyclerView inside another recyclerView

JavaAndroidAndroid RecyclerviewAndroid Cardview

Java Problem Overview


I'm planning to develop an app that shows some dynamic data inside a recyclerCardView. So i decided add a recyclerView called CheckBoxRecyclerView inside my main recyclerView. This is my code for my app :

My Main Activity :

setContentView(R.layout.activity_startup);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
reminderView = (RecyclerView) findViewById(R.id.reminder_recycler_view);
RlayoutManager = new LinearLayoutManager(this);
reminderView.setLayoutManager(RlayoutManager);

setSupportActionBar(toolbar);
cardView = (CardView) findViewById(R.id.card_first);
cardView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(getApplicationContext() , ReminderActivity.class);
        startActivity(intent);
    }
});
ReminderHelper helper = new ReminderHelper(getApplicationContext());
ReminderAdapter reminderAdapter = new ReminderAdapter(helper);
ContentValues reminderValues = new ContentValues();
ContentValues checkboxValues = new ContentValues();
// Devlopment Part ->
reminderValues.put("reminderTitle" , "A Reminder Title");
reminderValues.put("reminderLastModDate" , 0);
reminderValues.put("reminderAlarm" , 0);
reminderValues.put("reminderPicURI" , "skjshksjh");
reminderValues.put("ReminderBackground" , "#00796b");
checkboxValues.put("checkboxText" , "This is a CheckBox");
checkboxValues.put("isDone" , false);
checkboxValues.put("checkboxReminderID" , 0);
reminderAdapter.INSERT_REMINDER(reminderValues);
reminderAdapter.INSERT_CHECKBOX(checkboxValues);
File dbPath = getApplicationContext().getDatabasePath(ReminderHelper.DATABASE_NAME);
if(dbPath.exists()){
    List<Reminder> reminders = new ReminderAdapter(helper).getAllReminders();
    List<CheckBoxItem> checkBoxItems = new ReminderAdapter(helper).getAllCheckBoxes();
    RAdapter = new RAdapter(reminders , getApplicationContext() , checkBoxItems);
    reminderView.setAdapter(RAdapter);
}else{

}

And it's layout file :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="8dp"
    android:paddingLeft="8dp"
    android:paddingRight="8dp"
    android:paddingTop="8dp"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.smflog.sreminder.StartupActivity"
    tools:showIn="@layout/app_bar_startup">

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:id="@+id/reminder_recycler_view"
        android:scrollbars="vertical"
        android:layout_height="match_parent">

and inside this recyclerView there is another:

<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/reminder_card"
    card_view:cardCornerRadius="2dp"
    card_view:cardElevation="4dp"
    card_view:cardUseCompatPadding="true">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:paddingBottom="16dp"
        android:paddingLeft="8dp">

        <com.smflog.sreminder.utils.TitleView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/reminder_title"
            android:paddingTop="8dp"
            android:text="Wellcome To Google Keep !"
            android:textSize="15dp"
            android:textStyle="bold" />
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" android:orientation="horizontal">
<android.support.v7.widget.RecyclerView
    android:layout_width="wrap_content"
    android:id="@+id/checkbox_recycler_view"
    android:layout_height="wrap_content">

</android.support.v7.widget.RecyclerView>
        </LinearLayout>

    </LinearLayout>
</android.support.v7.widget.CardView>

Their adapters, Main ( RAdapter ) :

public class RAdapter extends RecyclerView.Adapter<RAdapter.ViewHolder> {
    List<Reminder> reminder;
    private Context context;
    private LinearLayoutManager lln;
    private CAdapter checkBoxAdapter;
    private List<CheckBoxItem> checkBoxItems;
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public CardView rCardView;
        public RecyclerView recyclerView;
        public TitleView rTitleView;
        public ViewHolder(View itemView) {
            super(itemView);
            rCardView = (CardView) itemView.findViewById(R.id.reminder_card);
            rTitleView = (TitleView) itemView.findViewById(R.id.reminder_title);
            recyclerView = (RecyclerView) itemView.findViewById(R.id.checkbox_recycler_view);
        }
    }

    public RAdapter(List<Reminder> reminder, Context context, List<CheckBoxItem> checkBoxItems) {
        this.reminder = reminder;
        this.context = context;
        this.checkBoxItems = checkBoxItems;
    }

    @Override
    public RAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.reminder_card, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(RAdapter.ViewHolder holder, int position) {
        lln = new LinearLayoutManager(context);
        holder.recyclerView.setLayoutManager(lln);
        checkBoxAdapter = new CAdapter(checkBoxItems, context);
        holder.recyclerView.setAdapter(checkBoxAdapter);
        holder.rCardView.setCardBackgroundColor(Color.parseColor("#00796b"));
        holder.rTitleView.setText(reminder.get(position).getReminderTitle());
    }

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

And second Adapter :

public class CAdapter extends RecyclerView.Adapter<CAdapter.ViewHolder> {
    List<CheckBoxItem> checkBoxItems;
    Context context;

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TitleView checkBoxTitle;
        public ImageView deleteCheckBox;
        public CheckBox checkBoxCheckBox;

        public ViewHolder(View itemView) {
            super(itemView);
            checkBoxTitle = (TitleView) itemView.findViewById(R.id.checkbox_item_text);
            checkBoxCheckBox = (CheckBox) itemView.findViewById(R.id.checkbox_item_checkbox);
            Log.d("CAdapterLog", "Adpater Holded !!!!! :( ");
            deleteCheckBox = (ImageView) itemView.findViewById(R.id.btn_delete_checkbox);
        }
    }

    public CAdapter(List<CheckBoxItem> checkBoxItems, Context context) {
        this.checkBoxItems = checkBoxItems;
        this.context = context;
        Log.d("CAdapterLog", "Adpater Created !!!!! :( ");
    }


    @Override
    public CAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.checkbox_item, parent, false);
        ViewHolder vh = new ViewHolder(v);
        Log.d("CAdapterLog", "Adpater ViewHolded :( !!!!! :( ");
        return vh;
    }

    @Override
    public void onBindViewHolder(CAdapter.ViewHolder holder, int position) {
        Boolean isCheckboxChecked = Boolean.parseBoolean(checkBoxItems.get(position).getCheckBoxIsDone());
        String checkBoxText = checkBoxItems.get(position).getCheckBoxBody();
        Log.d("CAdapterLog", "Adpater Binded :( ");
        final int checkboxID = Integer.parseInt(checkBoxItems.get(position).getCheckBoxID());
        int reminderCheckBoxID = Integer.parseInt(checkBoxItems.get(position).getCheckBoxReminderID());
        holder.deleteCheckBox.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("CAdapterLog", "Cross Button Clicked !");
            }
        });
        holder.checkBoxCheckBox.setChecked(isCheckboxChecked);
        holder.checkBoxTitle.setText(checkBoxText);
    }

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

}

And my problem: as you see in CAdapter, only constructor's Log message displayed.

UPDATE: if there is another way to display some dynamic data inside another dynamic card if yes is it better to use it instead of recyclerView?
anyone help me?
The output : Application output as you see just the setTitle for RAdapter works.

Java Solutions


Solution 1 - Java

I would like to suggest to use a single RecyclerView and populate your list items dynamically. I've added a github project to describe how this can be done. You might have a look. While the other solutions will work just fine, I would like to suggest, this is a much faster and efficient way of showing multiple lists in a RecyclerView.

The idea is to add logic in your onCreateViewHolder and onBindViewHolder method so that you can inflate proper view for the exact positions in your RecyclerView.

I've added a sample project along with that wiki too. You might clone and check what it does. For convenience, I am posting the adapter that I have used.

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

    private static final int FOOTER_VIEW = 1;
    private static final int FIRST_LIST_ITEM_VIEW = 2;
    private static final int FIRST_LIST_HEADER_VIEW = 3;
    private static final int SECOND_LIST_ITEM_VIEW = 4;
    private static final int SECOND_LIST_HEADER_VIEW = 5;

    private ArrayList<ListObject> firstList = new ArrayList<ListObject>();
    private ArrayList<ListObject> secondList = new ArrayList<ListObject>();

    public DynamicListAdapter() {
    }

    public void setFirstList(ArrayList<ListObject> firstList) {
        this.firstList = firstList;
    }

    public void setSecondList(ArrayList<ListObject> secondList) {
        this.secondList = secondList;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        // List items of first list
        private TextView mTextDescription1;
        private TextView mListItemTitle1;

        // List items of second list
        private TextView mTextDescription2;
        private TextView mListItemTitle2;

        // Element of footer view
        private TextView footerTextView;

        public ViewHolder(final View itemView) {
            super(itemView);

            // Get the view of the elements of first list
            mTextDescription1 = (TextView) itemView.findViewById(R.id.description1);
            mListItemTitle1 = (TextView) itemView.findViewById(R.id.title1);

            // Get the view of the elements of second list
            mTextDescription2 = (TextView) itemView.findViewById(R.id.description2);
            mListItemTitle2 = (TextView) itemView.findViewById(R.id.title2);

            // Get the view of the footer elements
            footerTextView = (TextView) itemView.findViewById(R.id.footer);
        }

        public void bindViewSecondList(int pos) {

            if (firstList == null) pos = pos - 1;
            else {
                if (firstList.size() == 0) pos = pos - 1;
                else pos = pos - firstList.size() - 2;
            }

            final String description = secondList.get(pos).getDescription();
            final String title = secondList.get(pos).getTitle();

            mTextDescription2.setText(description);
            mListItemTitle2.setText(title);
        }

        public void bindViewFirstList(int pos) {

            // Decrease pos by 1 as there is a header view now.
            pos = pos - 1;

            final String description = firstList.get(pos).getDescription();
            final String title = firstList.get(pos).getTitle();

            mTextDescription1.setText(description);
            mListItemTitle1.setText(title);
        }

        public void bindViewFooter(int pos) {
            footerTextView.setText("This is footer");
        }
    }

    public class FooterViewHolder extends ViewHolder {
        public FooterViewHolder(View itemView) {
            super(itemView);
        }
    }

    private class FirstListHeaderViewHolder extends ViewHolder {
        public FirstListHeaderViewHolder(View itemView) {
            super(itemView);
        }
    }

    private class FirstListItemViewHolder extends ViewHolder {
        public FirstListItemViewHolder(View itemView) {
            super(itemView);
        }
    }

    private class SecondListHeaderViewHolder extends ViewHolder {
        public SecondListHeaderViewHolder(View itemView) {
            super(itemView);
        }
    }

    private class SecondListItemViewHolder extends ViewHolder {
        public SecondListItemViewHolder(View itemView) {
            super(itemView);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == FOOTER_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_footer, parent, false);
            FooterViewHolder vh = new FooterViewHolder(v);
            return vh;

        } else if (viewType == FIRST_LIST_ITEM_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_first_list, parent, false);
            FirstListItemViewHolder vh = new FirstListItemViewHolder(v);
            return vh;

        } else if (viewType == FIRST_LIST_HEADER_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_first_list_header, parent, false);
            FirstListHeaderViewHolder vh = new FirstListHeaderViewHolder(v);
            return vh;

        } else if (viewType == SECOND_LIST_HEADER_VIEW) {
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_second_list_header, parent, false);
            SecondListHeaderViewHolder vh = new SecondListHeaderViewHolder(v);
            return vh;

        } else {
            // SECOND_LIST_ITEM_VIEW
            v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_second_list, parent, false);
            SecondListItemViewHolder vh = new SecondListItemViewHolder(v);
            return vh;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof SecondListItemViewHolder) {
                SecondListItemViewHolder vh = (SecondListItemViewHolder) holder;
                vh.bindViewSecondList(position);

            } else if (holder instanceof FirstListHeaderViewHolder) {
                FirstListHeaderViewHolder vh = (FirstListHeaderViewHolder) holder;

            } else if (holder instanceof FirstListItemViewHolder) {
                FirstListItemViewHolder vh = (FirstListItemViewHolder) holder;
                vh.bindViewFirstList(position);

            } else if (holder instanceof SecondListHeaderViewHolder) {
                SecondListHeaderViewHolder vh = (SecondListHeaderViewHolder) holder;

            } else if (holder instanceof FooterViewHolder) {
                FooterViewHolder vh = (FooterViewHolder) holder;
                vh.bindViewFooter(position);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public int getItemCount() {

        int firstListSize = 0;
        int secondListSize = 0;

        if (secondList == null && firstList == null) return 0;

        if (secondList != null)
            secondListSize = secondList.size();
        if (firstList != null)
            firstListSize = firstList.size();

        if (secondListSize > 0 && firstListSize > 0)
            return 1 + firstListSize + 1 + secondListSize + 1;   // first list header, first list size, second list header , second list size, footer
        else if (secondListSize > 0 && firstListSize == 0)
            return 1 + secondListSize + 1;                       // second list header, second list size, footer
        else if (secondListSize == 0 && firstListSize > 0)
            return 1 + firstListSize;                            // first list header , first list size
        else return 0;
    }

    @Override
    public int getItemViewType(int position) {

        int firstListSize = 0;
        int secondListSize = 0;

        if (secondList == null && firstList == null)
            return super.getItemViewType(position);

        if (secondList != null)
            secondListSize = secondList.size();
        if (firstList != null)
            firstListSize = firstList.size();

        if (secondListSize > 0 && firstListSize > 0) {
            if (position == 0) return FIRST_LIST_HEADER_VIEW;
            else if (position == firstListSize + 1)
                return SECOND_LIST_HEADER_VIEW;
            else if (position == secondListSize + 1 + firstListSize + 1)
                return FOOTER_VIEW;
            else if (position > firstListSize + 1)
                return SECOND_LIST_ITEM_VIEW;
            else return FIRST_LIST_ITEM_VIEW;

        } else if (secondListSize > 0 && firstListSize == 0) {
            if (position == 0) return SECOND_LIST_HEADER_VIEW;
            else if (position == secondListSize + 1) return FOOTER_VIEW;
            else return SECOND_LIST_ITEM_VIEW;

        } else if (secondListSize == 0 && firstListSize > 0) {
            if (position == 0) return FIRST_LIST_HEADER_VIEW;
            else return FIRST_LIST_ITEM_VIEW;
        }

        return super.getItemViewType(position);
    }
}

There is another way of keeping your items in a single ArrayList of objects so that you can set an attribute tagging the items to indicate which item is from first list and which one belongs to second list. Then pass that ArrayList into your RecyclerView and then implement the logic inside adapter to populate them dynamically.

Hope that helps.

Solution 2 - Java

I ran into similar problem a while back and what was happening in my case was the outer recycler view was working perfectly fine but the the adapter of inner/second recycler view had minor issues all the methods like constructor got initiated and even getCount() method was being called, although the final methods responsible to generate view ie..

1. onBindViewHolder() methods never got called. --> Problem 1.

2. When it got called finally it never show the list items/rows of recycler view. --> Problem 2.

Reason why this happened :: When you put a recycler view inside another recycler view, then height of the first/outer recycler view is not auto adjusted. It is defined when the first/outer view is created and then it remains fixed. At that point your second/inner recycler view has not yet loaded its items and thus its height is set as zero and never changes even when it gets data. Then when onBindViewHolder() in your second/inner recycler view is called, it gets items but it doesn't have the space to show them because its height is still zero. So the items in the second recycler view are never shown even when the onBindViewHolder() has added them to it.

Solution :: you have to create your custom LinearLayoutManager for the second recycler view and that is it. To create your own LinearLayoutManager: Create a Java class with the name CustomLinearLayoutManager and paste the code below into it. NO CHANGES REQUIRED

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(position);

            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();
        }
    }
}

Solution 3 - Java

you can use LayoutInflater to inflate your dynamic data as a layout file.

UPDATE : first create a LinearLayout inside your CardView's layout and assign an ID for it. after that create a layout file that you want to inflate. at last in your onBindViewHolder method in your "RAdaper" class. write these codes :

  mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

  view = mInflater.inflate(R.layout.my_list_custom_row, parent, false);

after that you can initialize data and ClickListeners with your RAdapter Data. hope it helps.

this and this may useful :)

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
QuestionMohammad FatemiView Question on Stackoverflow
Solution 1 - JavaReaz MurshedView Answer on Stackoverflow
Solution 2 - JavaKhayView Answer on Stackoverflow
Solution 3 - Javauser5747362View Answer on Stackoverflow