0

Good morning everyone, I hope you're doing great. I'm working with Room database with two tables or entities and want to display the list of all courses taken by a student in a recycler View using paging librar. In bind() method of the adapter, when I first used binding.courseName.text = studentCourse.courses.courseName.toString() to see the output after saving four courses in the Database and the name of a student who has taken these courses, it returns an array like this:

**[Course(courseName=Android, courseDuration=50), Course(courseName=Python, courseDuration=40), Course(courseName=Kotlin, courseDuration=36), Course(courseName=English, courseDuration=25)]

With the code below, it returns only one course with its name and duration in the recycler view and that is the last course of the array .

The two tables or entities Course and Student are defined like this:

@Entity(tableName = "course_table")
data class Course(
    @PrimaryKey(autoGenerate = false)
    val courseName : String,

    @ColumnInfo(name = "course_duration")
    val courseDuration : String
)
@Entity(tableName = "student_table")
data class Student(
    @PrimaryKey(autoGenerate = false)
    val studentName : String,

    val semester : Int,

    val schoolName : String
)

The relations between these two classes are represented by StudentAndCourse and CourseAndStudent** classes defined like:

data class StudentAndCourse(
    @Embedded
    val student : Student,

    @Relation(
        parentColumn = "studentName",
        entityColumn = "courseName",
        associateBy = Junction(StudentAndCourseTogether::class)
    )

    val courses : List<Course>
)
data class CourseAndStudent(
    @Embedded
    val course : Course,

    @Relation(
        parentColumn = "courseName",
        entityColumn = "studentName",
        associateBy = Junction(StudentAndCourseTogether::class)
    )

    val students : List<Student>
)

The query I defined in Dao to get all courses taken by a specific student identified by his name is :

@Query("SELECT * FROM student_table WHERE studentName = :studentName")
    fun getAllCoursesByStudentName(studentName: String) : PagingSource<Int, StudentAndCourse>```


Here is my Paging Adapter class:

class CourseByStudentNameAdapter : PagingDataAdapter<StudentAndCourse, CourseByStudentNameAdapter.CourseByStudentNameViewHolder>(DiffCallback) {

class CourseByStudentNameViewHolder(private val binding: CourseByStudentNameItemBinding) :
    RecyclerView.ViewHolder(binding.root){

    fun bind(studentAndCourse: StudentAndCourse){
        for (course in studentAndCourse.courses){
            binding.courseName.text = course.courseName
            binding.courseDuration.text = course.courseDuration
        }
    }
}

override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int,
): CourseByStudentNameAdapter.CourseByStudentNameViewHolder {
    val inflatedLayout = CourseByStudentNameItemBinding.inflate(LayoutInflater.from(
        parent.context), parent, false)

    return CourseByStudentNameViewHolder(inflatedLayout)
}


override fun onBindViewHolder(holder: CourseByStudentNameAdapter.CourseByStudentNameViewHolder, position: Int) {
    val currentCourse = getItem(position)

    if (currentCourse != null) {
        holder.bind(currentCourse)
    }
}



companion object DiffCallback : DiffUtil.ItemCallback<StudentAndCourse>(){
    override fun areItemsTheSame(
        oldItem: StudentAndCourse,
        newItem: StudentAndCourse
    ): Boolean = oldItem.courses == newItem.courses

    override fun areContentsTheSame(
        oldItem: StudentAndCourse,
        newItem: StudentAndCourse
    ): Boolean = oldItem == newItem

}

}```

My XML file that holds items:

<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <androidx.appcompat.widget.LinearLayoutCompat
        android:id="@+id/linear_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center"
        android:layout_marginTop="4dp"
        style="@style/itemListTextStyle"
        android:background="@drawable/item_layout_background"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">

        <TextView
            android:id="@+id/course_name"
            android:layout_width="match_parent"
            tools:text="Computer"
            android:gravity="center"
            android:layout_height="wrap_content"/>

        <TextView
            android:id="@+id/course_duration"
            android:layout_width="match_parent"
            tools:text="15 hours"
            android:gravity="center"
            android:layout_height="wrap_content"/>

    </androidx.appcompat.widget.LinearLayoutCompat>

</androidx.constraintlayout.widget.ConstraintLayout>```



My fragment code:

```
@AndroidEntryPoint
class AllCourseByStudentNameFragment : Fragment() {

    private var _binding : FragmentAllCourseByStudentNameBinding? = null
    private val binding get() = _binding!!

    private val viewModel : SchoolViewModel by activityViewModels()

    private lateinit var adapter : CourseByStudentNameAdapter

    private val schoolName = "IFRI"
    private val studentName = "Esperant"
    private val courseName = "Android"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setHasOptionsMenu(true)
    }


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentAllCourseByStudentNameBinding.inflate(inflater)
        return binding.root
    }

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

        adapter = CourseByStudentNameAdapter()
        binding.apply {
            recyclerView.adapter = adapter
            recyclerView.layoutManager = LinearLayoutManager(requireContext())
            recyclerView.setHasFixedSize(true)
        }

        viewModel.setName(studentName)
        viewModel.courseByStudentName.observe(viewLifecycleOwner){
            adapter.submitData(viewLifecycleOwner.lifecycle, it)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()

        _binding = null
    }
}```

I will be very glad to get your help. Thanks in advance.




1 Answers1

0

The problem would be in your ViewHolder. The ViewHolder represents a single item in your RecyclerView, not all.

The way you implemented it:

class CourseByStudentNameViewHolder(private val binding: CourseByStudentNameItemBinding) :
    RecyclerView.ViewHolder(binding.root){

    fun bind(studentAndCourse: StudentAndCourse){
        for (course in studentAndCourse.courses){
            binding.courseName.text = course.courseName
            binding.courseDuration.text = course.courseDuration
        }
    }
}

As you pass the StudentAndCourse item (which is a single item), your adapter will think there is only 1 item to display. Your ViewHolder should be used to bind the data of a single RecyclerView item (Course). The way you implemented it, you loop over all the courses and for every loop you override the courseName and courseDuration again. Therefore, in the end only the values of the last course will be visible as all previous courses are overwritten.

Instead of using your StudentAndCourse in the adapter, try to pass for example a single Course for each course in the StudentAndCourse object, so the adapter knows there is more than 1 item in the list.

class CourseByStudentNameAdapter : PagingDataAdapter<Course, CourseByStudentNameAdapter.CourseByStudentNameViewHolder>(DiffCallback) {

    class CourseByStudentNameViewHolder(private val binding: CourseByStudentNameItemBinding) : RecyclerView.ViewHolder(binding.root){
    
        fun bind(course: Course){
            binding.courseName.text = course.courseName
            binding.courseDuration.text = course.courseDuration
        }
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int,
    ): CourseByStudentNameAdapter.CourseByStudentNameViewHolder {
        val inflatedLayout = CourseByStudentNameItemBinding.inflate(LayoutInflater.from(
            parent.context), parent, false)

        return CourseByStudentNameViewHolder(inflatedLayout)
    }


    override fun onBindViewHolder(holder: CourseByStudentNameAdapter.CourseByStudentNameViewHolder, position: Int) {
        val currentCourse = getItem(position)

        if (currentCourse != null) {
            holder.bind(currentCourse)
        }
    }


    companion object DiffCallback : DiffUtil.ItemCallback<Course>(){
        override fun areItemsTheSame(
            oldItem: Course,
            newItem: Course
        ): Boolean = oldItem.courseName == newItem.courseName

        override fun areContentsTheSame(
            oldItem: Course,
            newItem: Course
        ): Boolean = oldItem == newItem

    }
}

You can take a look at the official documentation for paged data: Documentation

Ilias
  • 210
  • 3
  • 11
  • Okay thank you so much for your suggestion. I've tried this method, now, I need to find a way to pass only the courses list in the adapter from the observer (fragment). – Esperant GADA Mar 22 '22 at 08:14