Search code examples
androidkotlinandroid-fragmentsandroid-recyclerview

RecyclerView not showing up in Fragment


I am trying to add a RecyclerView to a fragment, but without success. I have followed this guide, but in there the RecyclerView is added to MainActivity, so I tried to adapt it. However, I cannot get the RecyclerView to show up.

I have already looked at similar questions, for example this one.

Here is the fragment_add_recipe.xml where the RecyclerView should appear:

<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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"
    tools:context=".AddRecipeFragment">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">


        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/recipe_name_text_input_layout"
            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent">


            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/recipe_name_text_input"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:inputType="text"
                android:hint="@string/recipe_name_hint"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>

        </com.google.android.material.textfield.TextInputLayout>


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/ingredient_row_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:itemCount="5"
            tools:listitem="@layout/ingredient_row_view"
            app:layout_constraintTop_toBottomOf="@id/recipe_name_text_input_layout"/>

        <Button
            android:id="@+id/save_recipe_button"
            android:layout_width="150sp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20sp"
            android:text="Save"
            android:textColor="#ffffff"
            android:textSize="20sp"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/ingredient_row_recycler_view"
            app:layout_constraintBottom_toBottomOf="parent"/>


    </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

AddRecipeFragment.kt:

package com.example.recipy

import CustomAdapter

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.recipy.databinding.FragmentAddRecipeBinding


class AddRecipeFragment : Fragment() {

    private var _binding: FragmentAddRecipeBinding? = null

//    For the ingredient row
    private val ingredientList : ArrayList<IngredientRowViewModel> = ArrayList()
    private lateinit var customAdapter : CustomAdapter

    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        // Inflate the Fragment layout
        _binding = FragmentAddRecipeBinding.inflate(inflater, container, false)

        setAdapter()

        return binding.root
    }

    private fun setAdapter(){
        customAdapter = CustomAdapter(ingredientList)
        binding.ingredientRowRecyclerView.apply {
            layoutManager = LinearLayoutManager(requireContext())
            adapter = customAdapter
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // Make button save the recipe in the SQL db
        binding.saveRecipeButton.setOnClickListener{
//            Some code for SQL connection here
        }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

The IngredientRowViewModel.kt:

package com.example.recipy

data class IngredientRowViewModel(var ingredient: String) {

}

The CustomAdapter.kt:

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.recipy.IngredientRowViewModel
import com.example.recipy.R
import com.example.recipy.databinding.IngredientRowViewBinding

class CustomAdapter(private val ingredientList: ArrayList<IngredientRowViewModel>) : RecyclerView.Adapter<CustomAdapter.IngredientRowViewHolder>() {

    inner class IngredientRowViewHolder(private val binding : IngredientRowViewBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(ingredient : IngredientRowViewModel){
            binding.ingredient = ingredient
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): IngredientRowViewHolder =
        IngredientRowViewHolder(IngredientRowViewBinding.inflate(LayoutInflater.from(parent.context), parent, false))


    override fun onBindViewHolder(holder: IngredientRowViewHolder, position: Int) {
        holder.bind(ingredientList[position])
    }

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

and finally ingredient_row_view.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="ingredient"
            type="com.example.recipy.IngredientRowViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:padding="5dp">

        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/ingredient_name_text_input_layout"
            style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent">


            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/ingredient_name_text_input"
                android:layout_height="wrap_content"
                android:layout_width="match_parent"
                android:inputType="text"
                android:hint="@string/ingredient_name_hint"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>

        </com.google.android.material.textfield.TextInputLayout>

    </LinearLayout>

</layout>

Solution

  • I updated your code a little, As i mentioned earlier in the comments, you have to add some items to the list if you want to see it. You can add button for adding items or you can go with fixed size list.

    fragment_add_receipe.xml

    Add new button for adding ingredients into your list. If you don't need this and want fixed count list, just create the list with empty items, for instance like this:

    //This will show 5 empty text inputs in the recyclerView
    private val ingredientList: ArrayList<IngredientRowViewModel> = arrayListOf(
        IngredientRowViewModel(""),
        IngredientRowViewModel(""),
        IngredientRowViewModel(""),
        IngredientRowViewModel(""),
        IngredientRowViewModel(""),
    )
    

    fragment_add_receipe.xml with new button:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.core.widget.NestedScrollView 
    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"
    tools:context=".AddRecipeFragment">
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp">
    
    
        <com.google.android.material.textfield.TextInputLayout
            android:id="@+id/recipe_name_text_input_layout"
            
    style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintTop_toTopOf="parent">
    
    
            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/recipe_name_text_input"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Receipe name"
                android:inputType="text"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
    
        </com.google.android.material.textfield.TextInputLayout>
    
    
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/ingredient_row_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            
    app:layout_constraintTop_toBottomOf="@id/recipe_name_text_input_layout"
            tools:itemCount="5"
            tools:listitem="@layout/ingredient_row_view" />
    
        <!-- Add new button so you can add ingredients into your list -->
        <Button
            android:id="@+id/addIngredient"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20sp"
            android:text="Add Ingredient"
            android:textColor="#ffffff"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            
    app:layout_constraintTop_toBottomOf="@id/ingredient_row_recycler_view" />
    
    
        <Button
            android:id="@+id/save_recipe_button"
            android:layout_width="150sp"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginTop="20sp"
            android:text="Save"
            android:textColor="#ffffff"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/addIngredient" />
    
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.core.widget.NestedScrollView>
    

    AddRecipeFragment.kt

    set onClickListener to the addIngredient Button and notify your adapter when adding new ingredients. After you add it, RecyclerView will show one item (or more) with empty TextField.

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
    
        binding.addIngredient.setOnClickListener {
            //add new ingredient with empty name into your list so your item will be shown
            ingredientList.add(IngredientRowViewModel(ingredient = ""))
            //Use notifyItemInserted instead of notifyDataSetChanged for better performance
            if (ingredientList.size == 0)
                customAdapter.notifyItemInserted(0)
            else
                customAdapter.notifyItemInserted(ingredientList.size - 1)
        }
    
        ...
    
    }
    

    ingredient_row_view.xml

    This is just optional, you have android:layout_height="match_parent" which means single item would use to much space and you have to scroll in order to see second item, you should change it to wrap_content.

    Suggestions

    • Follow the tutorial you mentioned, it's all written in there.
    • Check the basics of RecyclerView and how it works. I just have feeling that you struggle with understanding it.