我的回收站视图无法正确显示项目。但是,这些项目已经就位,因为我可以单击它们并输入详细信息片段。当我加载页面时,我只能查看 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,大部分代码来自人工智能来源或在线。我多次遵循代码的逻辑,但似乎无法找到问题的根本原因。我对连续几天编码感到非常厌倦,我真的需要有人的帮助。感谢您的阅读。
您能分享一下您的 Recyclerview 的 Adapter 类吗?