RecyclerView displays random items

My Recycler View is unable to properly display items. However, the items are already in place, as I can click on them and enter the details fragment. When I load the page, I can only view 5 items, and scrolling down does not reveal any other items outside the final one at position 11. Additionally, when I search for items on the page, I am able to receive any item, even if they are not visible on the page. Filtering causes the correct items to show, but outside of the initial 5 displayed items, the rest are not visible (but are indeed there). Thanks in advance for reading.

ExercisesFragment

package com.example.workouttracker.ui.exercises

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.SearchView
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.workouttracker.R
import com.example.workouttracker.databinding.FragmentExercisesBinding

class ExercisesFragment : Fragment() {

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

    private lateinit var viewModel: ExercisesViewModel
    private lateinit var adapter: ExerciseAdapter

    private val args: ExercisesFragmentArgs by navArgs()
    private var selectedTypes: List<String> = emptyList()
    private var selectedBodyParts: List<String> = emptyList()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentExercisesBinding.inflate(inflater, container, false)
        val root: View = binding.root

        setupRecyclerView()
        setupViewModel()
        setupObservers()
        viewModel.clearExercises()
        viewModel.fetchExercises()

        // Apply filters if any
        selectedTypes = args.selectedTypes?.toList() ?: emptyList()
        selectedBodyParts = args.selectedBodyParts?.toList() ?: emptyList()

        Log.d("Type Filter Selections", selectedTypes.toString())
        Log.d("BodyPart Filter Selections", selectedBodyParts.toString())

        return root
    }

    private fun setupRecyclerView() {
        adapter = ExerciseAdapter(mutableListOf()) { exercise ->
            val action = ExercisesFragmentDirections.actionNavigationExercisesToNavigationExerciseDetails(exercise)
            findNavController().navigate(action)
        }
        binding.recyclerViewExercises.layoutManager = LinearLayoutManager(requireContext())
        binding.recyclerViewExercises.adapter = adapter

        binding.recyclerViewExercises.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                super.onScrolled(recyclerView, dx, dy)
                val layoutManager = recyclerView.layoutManager as LinearLayoutManager
                val visibleItemCount = layoutManager.childCount
                val totalItemCount = layoutManager.itemCount
                val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
                val LastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
                Log.d("RecyclerView", "LastVisibleItemPosition: $LastVisibleItemPosition")
                if (!viewModel.isLoading.value!! && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount && firstVisibleItemPosition >= 0) {
                    viewModel.loadNextChunk()
                }
            }
        })
    }

    private fun setupViewModel() {
        viewModel = ViewModelProvider(this)[ExercisesViewModel::class.java]
    }

    private fun setupObservers() {
        viewModel.exercises.observe(viewLifecycleOwner) { exercises ->
            Log.d("Exercises", "${exercises.size}")
            if (selectedTypes.isNotEmpty() || selectedBodyParts.isNotEmpty()) {
                filterExercises(selectedTypes, selectedBodyParts)
            }
            else
                Handler(Looper.getMainLooper()).post {
                    adapter.updateExercises(exercises)
                }


        }

        viewModel.isLoading.observe(viewLifecycleOwner) { isLoading ->
            binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE
        }

        viewModel.error.observe(viewLifecycleOwner) { error ->
            error?.let {
                Toast.makeText(context, it, Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val createButton = binding.addExerciseButton
        createButton.setOnClickListener {
            findNavController().navigate(R.id.action_navigation_exercises_to_navigation_create_exercise)
        }
        setupSearchBar()
        setupFilterButton()
    }

    private fun filterExercises(selectedTypes: List<String>, selectedBodyParts: List<String>) {
        viewModel.exercises.value?.let { exercises ->
            val filteredExercises = exercises.filter { exercise ->
                (selectedTypes.isEmpty() || exercise.type in selectedTypes) &&
                        (selectedBodyParts.isEmpty() || exercise.bodypart in selectedBodyParts)
            }
            Handler(Looper.getMainLooper()).post {
                adapter.updateExercises(filteredExercises)
            }

        }
    }

    private fun setupFilterButton() {
        val filterButton = binding.filterExercise
        filterButton.setOnClickListener {
            val bundle = Bundle().apply {
                putStringArray("selectedTypes", selectedTypes.toTypedArray())
                putStringArray("selectedBodyParts", selectedBodyParts.toTypedArray())
            }
            findNavController().navigate(R.id.action_navigation_exercise_to_navigation_filter_exercises, bundle)
        }
    }


    private fun setupSearchBar() {
        val searchBar: SearchView = binding.searchBar

        searchBar.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
            override fun onQueryTextSubmit(query: String?): Boolean {
                // Handle the query text submission
                query?.let {
                    Toast.makeText(context, "Searching for: $it", Toast.LENGTH_SHORT).show()
                }
                return false
            }

            override fun onQueryTextChange(newText: String?): Boolean {
                // Handle the query text change
                newText?.let {
                    val filteredExercises = viewModel.filterExercises(it, selectedTypes, selectedBodyParts)
                    adapter.updateExercises(filteredExercises)
                }
                return false
            }
        })
    }

    override fun onResume() {
        super.onResume()
        clearSearchView()
    }

    private fun clearSearchView() {
        binding.searchBar.setQuery("", false)
    }

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

ExercisesViewModel

package com.example.workouttracker.ui.exercises

import android.app.Application
import android.util.Log
import androidx.lifecycle.*
import com.example.workouttracker.ui.data.Exercise
import com.example.workouttracker.ui.data.ExerciseDatabase
import com.example.workouttracker.ui.data.ExerciseRepository
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import kotlinx.coroutines.launch

class ExercisesViewModel(application: Application) : AndroidViewModel(application) {
    private val exerciseRepository: ExerciseRepository
    private val _exercises = MutableLiveData<List<Exercise>>()
    val exercises: LiveData<List<Exercise>> get() = _exercises

    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> get() = _isLoading

    private val _error = MutableLiveData<String?>()
    val error: LiveData<String?> get() = _error

    private var allExercises = listOf<Exercise>()
    private var currentChunkIndex = 0
    private val chunkSize = 7
    private var isFirstFetch = true

    init {
        val exerciseDao = ExerciseDatabase.getDatabase(application).exerciseDao()
        exerciseRepository = ExerciseRepository(exerciseDao)
    }

    fun fetchExercises() {
        viewModelScope.launch {
            _isLoading.value = true
            try {
                val userId = Firebase.auth.currentUser?.uid
                if (userId != null) {
                    exerciseRepository.getAllExercises(userId,
                        onSuccess = { exercisesList ->
                            allExercises = exercisesList
                            currentChunkIndex = 0
                            loadNextChunk()
                            _isLoading.value = false
                        },
                        onFailure = {
                            fetchExercisesFromCache()
                        }
                    )
                } else {
                    fetchExercisesFromCache()
                }
            } catch (e: Exception) {
                fetchExercisesFromCache()
            }
        }
    }



    private fun fetchExercisesFromCache() {
        viewModelScope.launch {
            Log.d("isfirstfetch", "$isFirstFetch")
            allExercises = exerciseRepository.getAllExercisesFromCache()
            if (isFirstFetch) {
                currentChunkIndex = 0
                isFirstFetch = false
                loadNextChunk()
            }
            _isLoading.postValue(false)
        }
    }

    fun loadNextChunk() {
        if (currentChunkIndex * chunkSize >= allExercises.size) return

        val nextChunk = allExercises.drop(currentChunkIndex * chunkSize).take(chunkSize)
        val currentExercises = _exercises.value ?: emptyList()

        val uniqueExercises = (currentExercises + nextChunk).distinct()

        viewModelScope.launch {
            _exercises.value = uniqueExercises

        }

        currentChunkIndex++
    }

    fun filterExercises(query: String, selectedTypes: List<String>, selectedBodyParts: List<String>): List<Exercise> {
        return exercises.value?.filter { exercise ->
            (selectedTypes.isEmpty() || exercise.type in selectedTypes) &&
                    (selectedBodyParts.isEmpty() || exercise.bodypart in selectedBodyParts) &&
                    (exercise.title.contains(query, ignoreCase = true) ||
                            exercise.description.contains(query, ignoreCase = true))
        } ?: emptyList()
    }

    fun clearExercises() {
        _exercises.value = emptyList()
    }

}

ExerciseRepository

package com.example.workouttracker.ui.data

import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.PropertyName
import android.util.Log
import android.os.Parcel
import android.os.Parcelable
import com.google.firebase.firestore.Exclude
import kotlinx.coroutines.tasks.await

class ExerciseRepository(private val exerciseDao: ExerciseDao) {
    private val firestore = FirebaseFirestore.getInstance()
    private val defaultExercisesCollection = firestore.collection("exercises")

    // Add an exercise to a user's collection in Firestore
    fun addExercise(userId: String, exercise: Exercise) {
        val userExercisesCollection = firestore.collection("users").document(userId).collection("exercises")
        userExercisesCollection.add(exercise)
            .addOnSuccessListener { documentReference ->
                // Exercise added successfully
                val exerciseId = documentReference.id
                Log.d("ExerciseRepository", "Exercise added with ID: $exerciseId")
                // You can handle success as needed
            }
            .addOnFailureListener { e ->
                // Failed to add exercise
                Log.e("ExerciseRepository", "Error adding exercise", e)
            }
    }

    // Update an existing exercise in a user's collection in Firestore
    fun updateExercise(userId: String, exerciseId: String, updatedExercise: Exercise) {
        val exerciseRef = firestore.collection("users").document(userId).collection("exercises").document(exerciseId)
        exerciseRef.set(updatedExercise)
            .addOnSuccessListener {
                // Exercise updated successfully
                Log.d("ExerciseRepository", "Exercise updated with ID: $exerciseId")
                // You can handle success as needed
            }
            .addOnFailureListener { e ->
                // Failed to update exercise
                Log.e("ExerciseRepository", "Error updating exercise", e)
            }
    }

    // Delete an exercise from a user's collection in Firestore
    fun deleteExercise(userId: String, exerciseId: String) {
        val exerciseRef = firestore.collection("users").document(userId).collection("exercises").document(exerciseId)
        exerciseRef.delete()
            .addOnSuccessListener {
                // Exercise deleted successfully
                Log.d("ExerciseRepository", "Exercise deleted with ID: $exerciseId")
                // You can handle success as needed
            }
            .addOnFailureListener { e ->
                // Failed to delete exercise
                Log.e("ExerciseRepository", "Error deleting exercise", e)
            }
    }

    private fun combineExercises(defaultExercises: List<Exercise>, userExercises: List<Exercise>): List<Exercise> {
        val allExercises = defaultExercises.toMutableList()
        allExercises.addAll(userExercises)
        return allExercises
    }

    suspend fun getAllExercises(userId: String, onSuccess: (List<Exercise>) -> Unit, onFailure: (Exception) -> Unit) {
        val userExercisesCollection = firestore.collection("users").document(userId).collection("exercises")

        try {
            // Fetch from Firestore
            val userDocuments = userExercisesCollection.get().await()
            val userExercises = userDocuments.toObjects(Exercise::class.java)

            // Fetch default exercises from Firestore
            val defaultDocuments = defaultExercisesCollection.get().await()
            val defaultExercises = defaultDocuments.toObjects(Exercise::class.java)

            // Combine both lists
            val allExercises = combineExercises(defaultExercises, userExercises)

            // Clear cache and insert new exercises
            exerciseDao.clearExercises()
            exerciseDao.insertExercises(allExercises)

            Log.d("ExerciseRepository", "Fetched exercises from Firestore: $allExercises")
            onSuccess(allExercises)
        } catch (e: Exception) {
            onFailure(e)
        }
    }

    suspend fun getAllExercisesFromCache(): List<Exercise> {
        Log.d("ExerciseRepository", "Getting all exercises from cache")
        return exerciseDao.getAllExercises()
    }
}

fragment_exercises.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"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".ui.exercises.ExercisesFragment">

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

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/nav_view"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="?android:attr/windowBackground"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:menu="@menu/bottom_nav_menu" />

        <SearchView
            android:id="@+id/search_bar"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:iconifiedByDefault="false"
            android:queryHint="Search Exercises"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintWidth_percent="0.9"
            app:layout_constraintHorizontal_bias="0.5" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view_exercises"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:clipToPadding="false"
            android:nestedScrollingEnabled="false"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
            app:layout_constraintTop_toBottomOf="@id/search_bar"
            app:layout_constraintBottom_toTopOf="@id/nav_view"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <ImageView
            android:id="@+id/filter_exercise"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:contentDescription="@string/filter_exercises"
            android:src="@drawable/ic_filter_funnel"
            android:elevation="4dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintVertical_bias="0.0" />

        <Button
            android:id="@+id/add_exercise_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="12dp"
            android:text="@string/create_exercise"
            app:layout_constraintBottom_toTopOf="@id/nav_view"
            app:layout_constraintEnd_toEndOf="parent" />

        <ProgressBar
            android:id="@+id/progress_bar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

item_exercise.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="exercise"
            type="com.example.workouttracker.ui.data.Exercise" />
    </data>

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

        <TextView
            android:id="@+id/title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{exercise.title}"
            android:textSize="18dp"

            android:textStyle="bold"
            android:layout_marginBottom="8dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintEnd_toEndOf="parent" />

        <TextView
            android:id="@+id/description"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="@{exercise.description}"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/title"
            app:layout_constraintEnd_toEndOf="parent" />

        <!-- Add other views for animation and default if needed -->

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

I have spent days prompting GPT to solve my issue, introduced features to stop the adapter from binding items if they have been binded before (causes the items to not display completely, and my search exercises completely ceases to work.) I’m new to Kotlin, and the bulk of the code is from AI sources or online. I have followed the logic of the code various times but I cannot seem to find the root cause of the issue. I am very tired from coding for days on end and I really need help from someone. Thank you for reading.

New contributor

Ethan Lee is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật