You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView结合LiveData时Item点击响应异常问题求助

问题:RecyclerView Item点击需多次触发才能响应

开发Android应用时,使用RecyclerView展示LiveData提供的用户位置数据,但遇到RecyclerView Item点击需要3-4次才能触发响应的异常,希望找到解决方法使Item点击即时生效。


HomeFragment代码

package com.kttelematicsolutions.routeplay.fragments


import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.kttelematicsolutions.routeplay.LocationService
import com.kttelematicsolutions.routeplay.MainViewModel
import com.kttelematicsolutions.routeplay.R
import com.kttelematicsolutions.routeplay.adapters.LocationAdapter
import com.kttelematicsolutions.routeplay.databinding.FragmentHomeBinding
import com.kttelematicsolutions.routeplay.realmClasses.LocationData
import com.kttelematicsolutions.routeplay.realmClasses.User


class HomeFragment : Fragment(), LocationAdapter.OnItemClickListener {
    private lateinit var binding: FragmentHomeBinding
    private val viewModel: MainViewModel by viewModels()
    private lateinit var userLocationListAdapter: LocationAdapter
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private val navController by lazy { findNavController() }
    private var userId = ""


    override fun onStart() {
        super.onStart()
        viewModel.currentUser.observe(viewLifecycleOwner) { currentUser ->
            if (currentUser != null) {
                val serviceIntent = Intent(requireContext(), LocationService::class.java)
                serviceIntent.putExtra("userId", currentUser.username)
                requireContext().startService(serviceIntent)
            }
        }

    }

    override fun onStop() {
        super.onStop()
        requireContext().stopService(Intent(requireContext(), LocationService::class.java))

    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(requireActivity())
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        return binding.root
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val locationRecycler = view.findViewById<RecyclerView>(R.id.location_recycler)
        val layoutManager = LinearLayoutManager(requireContext())
        locationRecycler.layoutManager = layoutManager
        viewModel.currentUser()
        binding.usernameLayout.setOnClickListener {
            showUserListPopup()
        }
        viewModel.currentUser.observe(viewLifecycleOwner) {
            viewModel.currentUser()
            if (it != null) {
                bindViewData(it)
                it.username?.let { it1 ->
                    userId = it1
                    viewModel.getLocationOfUser(it1)
                }
            }
        }
        userLocationListAdapter = LocationAdapter(emptyList() , this)
        locationRecycler.adapter = userLocationListAdapter

        viewModel.usersLocationLiveData.observe(viewLifecycleOwner) { locationData ->
            userLocationListAdapter.updateData(locationData)
        }

    }

    private fun bindViewData(currentUser: User) {
        binding.usernameText.text = currentUser.username
        binding.title2.text = getString(R.string.location_log, currentUser.username)
    }

    private fun showUserListPopup() {
        val userListDialog = UserListDialogFragment()
        userListDialog.show(childFragmentManager, "UserListDialog")
    }

    override fun onItemClick(location : LocationData) {
        binding.progress.visibility = View.VISIBLE
        val bundle = Bundle()
        bundle.putString("id", location.id)
        bundle.putString("userId", location.userId)
        bundle.putDouble("latitude", location.latitude)
        bundle.putDouble("longitude", location.longitude)
        bundle.putString("timestamp", location.timestamp)
        Log.d("TAG", "onItemClick: works")
        navController.navigate(R.id.action_homeFragment_to_mapsFragment, bundle)
        Log.d("TAG", "onItemClick: works2")
    }
}

LocationAdapter代码

package com.kttelematicsolutions.routeplay.adapters

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageButton
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.recyclerview.widget.RecyclerView
import com.kttelematicsolutions.routeplay.realmClasses.LocationData
import com.kttelematicsolutions.routeplay.R

class LocationAdapter(
    private var locations: List<LocationData>,
    private var onItemClickListener: OnItemClickListener
) :
    RecyclerView.Adapter<LocationAdapter.LocationViewHolder>() {

    interface OnItemClickListener {
        fun onItemClick(location: LocationData)
    }
    fun updateData(updatedLocationData: List<LocationData>) {
        locations = updatedLocationData
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocationViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.location_log_item, parent, false)
        return LocationViewHolder(view)
    }

    override fun onBindViewHolder(holder: LocationViewHolder, position: Int) {
        val location = locations[position]
        holder.bind(location)

    }

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

    inner class LocationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val locationNameTextView: TextView = itemView.findViewById(R.id.location_name)
        private val cityNameTextView: TextView = itemView.findViewById(R.id.city_name)
        private val timeStampTextView: TextView = itemView.findViewById(R.id.time_stamp)
        private val viewButton: Button = itemView.findViewById(R.id.viewButton)
        private val locationLayout: RelativeLayout = itemView.findViewById(R.id.overlay)

        fun bind(location: LocationData) {
            locationNameTextView.text = location.latitude.toString()
            cityNameTextView.text = location.longitude.toString()
            timeStampTextView.text = location.timestamp
            viewButton.setOnClickListener {
                onItemClickListener.onItemClick(location)
            }
            locationLayout.setOnClickListener {
                onItemClickListener.onItemClick(location)
            }
        }
    }
}

MainViewModel代码

package com.kttelematicsolutions.routeplay

import android.location.Location
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kttelematicsolutions.routeplay.realmClasses.LocationData
import com.kttelematicsolutions.routeplay.realmClasses.Otp
import com.kttelematicsolutions.routeplay.realmClasses.RealmApplication
import com.kttelematicsolutions.routeplay.realmClasses.User
import io.realm.kotlin.UpdatePolicy
import io.realm.kotlin.delete
import io.realm.kotlin.ext.query
import io.realm.kotlin.query.RealmResults
import kotlinx.coroutines.launch


class MainViewModel : ViewModel() {
    private val realm = RealmApplication.realm
    private val _usersLiveData = MutableLiveData<List<User>>()
    val usersLiveData: LiveData<List<User>> = _usersLiveData
    private val _currentUser = MutableLiveData<User>()
    val currentUser : LiveData<User> = _currentUser
    private val _usersLocationLiveData = MutableLiveData<List<LocationData>>()
    val usersLocationLiveData: LiveData<List<LocationData>> = _usersLocationLiveData
    fun addUser(
        email : String ,
        username : String ,
        password : String ,
        otp : String
    ) {
        viewModelScope.launch {
            realm.write {
                val user = User().apply {
                    this.username = username
                    this.email = email
                    this.password = password
                }
                copyToRealm(user , updatePolicy = UpdatePolicy.ALL)
                val generateOtp = Otp().apply {
                    this.code = otp
                    this.id = user.email.toString()
                }
                copyToRealm(generateOtp , updatePolicy = UpdatePolicy.ALL)
            }
        }
    }

    fun verifyOtp(
        enteredOtp : String ,
        email : String ,
        callback : (success : Boolean) -> Unit
    ) {
        viewModelScope.launch {
            try {
                val otpObject = realm.query<Otp>("id == $0" , email).find().first()
                if (otpObject.code == enteredOtp) {
                    realm.write {
                        this.delete<Otp>() // Delete the used OTP
                    }
                    callback(true)
                    realm.query<User>("email == $0", email).find().first().also {
                        realm.writeBlocking {
                            findLatest(it)?.isLoggedIn = true
                        }
                    }

                } else {
                    callback(false)
                }
            } catch (exception : Exception) {
                Log.e("OtpViewModel" , "Error verifying user: ${exception.message}")
            }

        }
    }

    fun loginUser(email : String, password : String, otp : String, callback : (success : Boolean) -> Unit){
        viewModelScope.launch {
            try {
                val userObject = realm.query<User>("email == $0 AND password == $1", email, password).find().first()
                if(userObject.isLoggedIn){
                    val generateOtp = Otp().apply {
                        this.code = otp
                        this.id = email
                    }
                    realm.write {
                        copyToRealm(generateOtp , updatePolicy = UpdatePolicy.ALL)
                    }
                    realm.query<User>("email == $0", email).find().first().also {
                        realm.writeBlocking {
                            findLatest(it)?.isLoggedIn = true
                            findLatest(it)?.currentUser = true
                        }

                    }
                    callback(true)
                }else{
                    callback(false)
                }
            }catch (exception : Exception){
                callback(false)
                Log.e("LoginViewModel" , "User Login Failed: ${exception.message}")
            }
        }
    }
    fun checkUserAlreadyExists(email: String, username: String, callback : (success : Boolean) -> Unit){
        viewModelScope.launch {
            try {
                realm.query<User>("email == $0 OR username == $1", email, username).find().first()
                callback(true)
            }catch (exception : Exception){
                callback(false)
                Log.e("SignupViewModel" , "User not exists: ${exception.message}")
            }
        }
    }

    fun setCurrentUser(selectedUser: String) {
        viewModelScope.launch {
            try {
                realm.query<User>("email == $0", selectedUser).find().first().also {
                    realm.writeBlocking {
                        findLatest(it)?.currentUser = true
                    }
                    Log.d("SignupViewModel" , "User updated: Done")
                }
                val otherUsers = realm.query<User>("email != $0", selectedUser)
                for (user in otherUsers.find()) {
                    realm.write{
                        findLatest(user)?.currentUser = false
                    }
                }
                Log.d("SignupViewModel" , "Other User update: Done")
            }catch (exception : Exception){
                Log.e("SignupViewModel" , "User not exists: ${exception.message}")
            }

        }
    }
    fun getAllUsers() {
        viewModelScope.launch {
            val userResults: RealmResults<User> = realm.query<User>().find()
            val users = userResults.toList()
            _usersLiveData.postValue(users)
        }
    }
    fun currentUser(){
        viewModelScope.launch {
            try {
                val currentUserResult = realm.query<User>("currentUser==$0", true).find().first()
                _currentUser.postValue(currentUserResult)
            }catch (exception : Exception){
                Log.e("HomeViewModel" , "User not found: ${exception.message}")
            }

        }

    }
    fun saveLocationDataToRealm(userId: String, locationResult: Location, timestamp: String) {
        viewModelScope.launch {
            realm.write {
                val locationData = LocationData().apply {
                    this.userId = userId
                    this.latitude = locationResult.latitude
                    this.longitude = locationResult.longitude
                    this.timestamp = timestamp
                }
                copyToRealm(locationData, UpdatePolicy.ALL)
                Log.e("HomeViewModel" , "User location: $locationData")
            }
        }
    }
    fun getLocationOfUser(userId: String){
        viewModelScope.launch{
            try {
                val currentUserLocationResult = realm.query<LocationData>("userId==$0", userId).find().toList()
                _usersLocationLiveData.postValue(currentUserLocationResult)
            }catch (exception: Exception){
                Log.e("HomeViewModel" , "User location not found: ${exception.message}")
            }
        }
    }

    fun getPreviousLocations(userId: String, currentLocationId: String): LiveData<List<LocationData>> {
        val previousLocationsLiveData = MutableLiveData<List<LocationData>>()
        viewModelScope.launch {
            try {
                val previousLocationsResult = realm.query<LocationData>("userId == $0 AND id <= $1", userId, currentLocationId).find()

                previousLocationsLiveData.postValue(previousLocationsResult)
            } catch (exception: Exception) {
                Log.e("MainViewModel", "Error retrieving previous locations: ${exception.message}")
            }
        }
        return previousLocationsLiveData
    }
}

解决方法

1. 修复LiveData循环触发问题

在HomeFragment的onViewCreated方法中,viewModel.currentUser.observe回调内重复调用viewModel.currentUser()会导致LiveData循环发送数据,RecyclerView持续刷新打断点击响应,移除该重复调用:

viewModel.currentUser.observe(viewLifecycleOwner) {
    // 移除 viewModel.currentUser() 这行代码
    if (it != null) {
        bindViewData(it)
        it.username?.let { it1 ->
            userId = it1
            viewModel.getLocationOfUser(it1)
        }
    }
}

2. 优化Adapter数据更新逻辑

当前使用notifyDataSetChanged()会触发RecyclerView全量刷新,导致Item状态重置。改用AsyncListDiffer实现局部高效刷新:

class LocationAdapter(
    private var onItemClickListener: OnItemClickListener
) : RecyclerView.Adapter<LocationAdapter.LocationViewHolder>() {

    interface OnItemClickListener {
        fun onItemClick(location: LocationData)
    }

    private val diffCallback = object : DiffUtil.ItemCallback<LocationData>() {
        override fun areItemsTheSame(oldItem: LocationData, newItem: LocationData): Boolean {
            return oldItem.id == newItem.id // 依赖LocationData的唯一id判断
        }

        override fun areContentsTheSame(oldItem: LocationData, newItem: LocationData): Boolean {
            return oldItem.latitude == newItem.latitude &&
                    oldItem.longitude == newItem.longitude &&
                    oldItem.timestamp == newItem.timestamp &&
                    oldItem.userId == newItem.userId
        }
    }

    private val differ = AsyncListDiffer(this, diffCallback)

    fun updateData(updatedLocationData: List<LocationData>) {
        differ.submitList(updatedLocationData)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LocationViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.location_log_item, parent, false)
        return LocationViewHolder(view)
    }

    override fun onBindViewHolder(holder: LocationViewHolder, position: Int) {
        val location = differ.currentList[position]
        holder.bind(location)
    }

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

    inner class LocationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val locationNameTextView: TextView = itemView.findViewById(R.id.location_name)
        private val cityNameTextView: TextView = itemView.findViewById(R.id.city_name)
        private val timeStampTextView: TextView = itemView.findViewById(R.id.time_stamp)
        private val viewButton: Button = itemView.findViewById(R.id.viewButton)
        private val locationLayout: RelativeLayout = itemView.findViewById(R.id.overlay)

        fun bind(location: LocationData) {
            locationNameTextView.text = location.latitude.toString()
            cityNameTextView.text = location.longitude.toString()
            timeStampTextView.text = location.timestamp
            viewButton.setOnClickListener {
                onItemClickListener.onItemClick(location)
            }
            locationLayout.setOnClickListener {
                onItemClickListener.onItemClick(location)
            }
        }
    }
}

同时修改Fragment中Adapter的初始化代码:

userLocationListAdapter = LocationAdapter(this) // 移除初始空列表参数
locationRecycler.adapter = userLocationListAdapter

3. 避免重复添加LiveData观察者

将onStart中viewModel.currentUser.observe的逻辑移至onViewCreated,避免每次onStart都添加新观察者,减少不必要的回调:

@RequiresApi(Build.VERSION_CODES.O
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 07:15:43