RecyclerView 显示随机项目

问题描述 投票:0回答:1

我的回收站视图无法正确显示项目。但是,这些项目已经就位,因为我可以单击它们并输入详细信息片段。当我加载页面时,我只能查看 5 个项目,并且向下滚动不会显示位置 11 处最后一个项目之外的任何其他项目。此外,当我在页面上搜索项目时,我能够收到任何项目,甚至如果它们在页面上不可见。过滤会导致显示正确的项目,但除了最初显示的 5 个项目之外,其余项目不可见(但确实存在)。预先感谢您的阅读。

练习片段

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
    }
}

练习视图模型

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

}

练习存储库

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>

我花了几天时间提示 GPT 解决我的问题,引入了一些功能来阻止适配器绑定以前绑定过的项目(导致项目无法完全显示,并且我的搜索练习完全停止工作。)我是新的Kotlin,大部分代码来自人工智能来源或在线。我多次遵循代码的逻辑,但似乎无法找到问题的根本原因。我对连续几天编码感到非常厌倦,我真的需要有人的帮助。感谢您的阅读。

android kotlin android-recyclerview
1个回答
0
投票

您能分享一下您的 Recyclerview 的 Adapter 类吗?

© www.soinside.com 2019 - 2024. All rights reserved.