androidkotlinandroid-recyclerviewnestedrecyclerview

Nested Vertical RecyclerView doesn't show all contents. Works if child RecyclerView is horizontal


I have the following requirement - a vertical Recyclerview of List which contains a TextView and a child vertical RecyclerView of List. This data comes from an API. The issue is that the inner RV doesn't scroll and doesn't display all the items, only the first 4 or so. Ideally I want the child RV to display all the items. What's interesting is if I make the child RV horizontal, this problem goes away, I can fully scroll and see all the items.

I have the following implementation.

Parent

class TitlesAdapter(
private val titlesList: List<Title>,
private val articlesList: List<Article>
): RecyclerView.Adapter<TitlesAdapter.Holder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
    val binding = TitlesBinding.inflate(LayoutInflater.from(parent.context),parent,false)
    return TitlesBinding.Holder(binding)
}

override fun onBindViewHolder(holder: Holder, position: Int) {
    val itemRow = categoryList.get(position)
    holder.bindRow(itemRow, articlesList)
}

override fun getItemCount(): Int {
    return categoryList.size
}

class Holder(var binding: TitlesBinding): RecyclerView.ViewHolder(binding.root){

  
    fun bindRow(
        title: Title,
        articles: List<Article>
    ) {
            binding.tvTitleName.text = title.name
        val filteredList = articles.filter { it.category == title.id }

        binding.rvChild.adapter = ChildRVAdapter(filteredList)
        binding.rvChild.layoutManager = LinearLayoutManager(itemView.context)
      
    }
}
}

Child RV Adapter:

class ChildRVAdapter(private val articleList: List<Article>): RecyclerView.Adapter<ChildRVAdapter.Holder>() {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
    val binding = RowArticleBinding.inflate(LayoutInflater.from(parent.context),parent,false)
    return RowArticleBinding.Holder(binding)
}

override fun onBindViewHolder(holder: Holder, position: Int) {
    val itemRow = articleList.get(position)
    holder.bindRow(itemRow)
}

override fun getItemCount(): Int {
    return articleList.size
}

class Holder(var binding: RowArticleBinding): RecyclerView.ViewHolder(binding.root), View.OnClickListener{

    fun bindRow(
        article: Article
    ) {
        binding.tvTitle.text = article.title
        binding.tvDesc.text = article.description
    }
}
}

The Parent RV's xml looks like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
    android:id="@+id/tv_title_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="20sp"
    android:textColor="@color/textBlack"
    android:fontFamily="@font/playfair_bold"
    android:layout_marginBottom="18dp"
    android:text="Introduction"/>
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/rv_child"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>
</LinearLayout>

And the child RV's XML:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">


    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Introduction from Annabel Karmel"
        android:textColor="@color/textBlack"
        android:textSize="14sp"
        android:fontFamily="@font/montserrat_bold"/>
    <TextView
        android:id="@+id/tv_desc"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/dustyGrey"
        android:layout_marginTop="6dp"
        android:textSize="14sp"
        android:fontFamily="@font/montserrat_medium"/>
</LinearLayout>

Solution

  • At a guess it's because your parent list's items have a fixed height of match_parent (i.e. whatever height the parent RecyclerView happens to occupy) while the child RV's height is wrap_content, i.e. "as tall as necessary to display all its contents without scrolling". So the child RV will be displaying everything at once, but with all but the first 4 items off the bottom of the screen - you can use the Layout Inspector while your app is running to see what's going on.

    With scrollable content (e.g. RecyclerViews, ScrollViews etc.) you need to limit that container's height to a "window" where you want to display content. If the content is bigger than that window, then it'll scroll. Generally wrap_content on the axis you want to scroll is an error, because it will prevent it from scrolling! And you'll end up with a large view that's too big to be fully displayed.

    So instead, the RecyclerView in your parent RV file should have a constrained height, like 0dp with layout_weight="1" in a LinearLayout so it fills the remaining available space in its parent layout. That way, if its contents are too big, it will remain at that fixed size and scroll instead of expanding.