BottomSheetDialogFragment中RecyclerView数据无法实时更新求助
问题
在Android应用中,实现了包含RecyclerView的BottomSheetDialogFragment,RecyclerView的数据通过Socket连接逐个接收,需要动态添加数据并为每个项通过ViewModel设置计时器。核心问题是:新数据添加后,RecyclerView的UI无法实时更新,只有当Fragment或RecyclerView状态变更时才会显示变化。Socket异步运行,UI更新延迟或不显示,即便尝试过notifyItemInserted()等方法也无法解决。
执行流程:
- 通过Socket逐个接收数据
- 将数据添加到ViewModel的列表中
- 更新RecyclerView适配器
- 为新增项设置计时器
代码片段
BottomSheetDialogFragment代码
class RideRequestsBottomSheet( private val context: Context, private var viewModel: RideRequestViewModel, private val onRideRequestClickListener: OnRideRequestClickListener ) : BottomSheetDialogFragment() { private lateinit var binding: BottomSheetDialogCustomerDetailsBinding private lateinit var adapter: RideRequestAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { binding = BottomSheetDialogCustomerDetailsBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) adapter = RideRequestAdapter(context, onRideRequestClickListener, viewModel) binding.rvRideRequests.layoutManager = LinearLayoutManager(context) binding.rvRideRequests.adapter = adapter viewModel.rideRequests.observe(viewLifecycleOwner) { rideRequests -> adapter.updateData(rideRequests) } viewModel.timers.observe(viewLifecycleOwner) { timers -> adapter.updateTimers(timers) } } }
Socket数据监听代码
socketService.listenToRideRequestEvent(object : SocketCallback<ReceiveRideRequestResponse> { override fun onListen(data: ReceiveRideRequestResponse) { openBottomSheet() rideRequestViewModel.addRideRequest(data) } override fun onError(error: Throwable) { // Handle errors here if needed } })
RideRequestAdapter代码
class RideRequestAdapter( private val context: Context, private val onRideRequestClickListener: OnRideRequestClickListener, private val viewModel: RideRequestViewModel ) : RecyclerView.Adapter<RideRequestAdapter.RideRequestViewHolder>() { private var dataList: List<ReceiveRideRequestResponse> = emptyList() private val timerData: MutableMap<String, Long> = mutableMapOf() inner class RideRequestViewHolder(val binding: ItemRequestRidesBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RideRequestViewHolder { val binding = ItemRequestRidesBinding.inflate(LayoutInflater.from(context), parent, false) return RideRequestViewHolder(binding) } override fun onBindViewHolder(holder: RideRequestViewHolder, position: Int) { val binding = holder.binding val item = dataList[position] binding.tvFare.text = context.getString(R.string.price, item.fare.toString()) // Update timer from ViewModel val remainingTime = timerData[item.rideRequestId] ?: 0 binding.tvTimer.text = "${remainingTime / 1000}" // Handle accept/reject buttons binding.mbAcceptRide.setOnClickListener { onRideRequestClickListener.onAccept(item) viewModel.removeRideRequest(item.rideRequestId!!) } binding.mbRejectRide.setOnClickListener { onRideRequestClickListener.onReject(item) viewModel.removeRideRequest(item.rideRequestId!!) } } override fun getItemCount(): Int = dataList.size fun updateData(newData: List<ReceiveRideRequestResponse>) { this.dataList = newData notifyDataSetChanged() } fun updateTimers(newTimers: Map<String, Long>) { this.timerData.clear() this.timerData.putAll(newTimers) notifyDataSetChanged() } }
ViewModel代码
class RideRequestViewModel : ViewModel() { private val _rideRequests = MutableLiveData<MutableList<ReceiveRideRequestResponse>>() val rideRequests: LiveData<MutableList<ReceiveRideRequestResponse>> = _rideRequests private val _timers = MutableLiveData<Map<String, Long>>() val timers: LiveData<Map<String, Long>> = _timers private val timerJobs = mutableMapOf<String, Job>() init { _rideRequests.value = mutableListOf() _timers.value = emptyMap() } fun addRideRequest(request: ReceiveRideRequestResponse) { _rideRequests.value?.let { it.add(request) _rideRequests.value = it } startTimer(request.rideRequestId!!) } // Start a timer for a specific rideRequestId private fun startTimer(rideRequestId: String) { val timerDuration = 25000L // 25 seconds timerJobs[rideRequestId] = viewModelScope.launch { var remainingTime = timerDuration while (remainingTime > 0) { delay(100L) remainingTime -= 100L _timers.value = _timers.value.orEmpty().toMutableMap().apply { this[rideRequestId] = remainingTime } } removeRideRequest(rideRequestId) } } fun removeRideRequest(rideRequestId: String) { timerJobs[rideRequestId]?.cancel() timerJobs.remove(rideRequestId) _rideRequests.value?.let { val updatedList = it.filter { request -> request.rideRequestId != rideRequestId } _rideRequests.value = updatedList.toMutableList() } } }
解决方案
1. 修复LiveData更新逻辑,确保触发观察者回调
当前ViewModel中直接修改原有列表再赋值,LiveData无法识别列表变更(引用未变)。修改为创建新列表实例并使用postValue确保主线程更新:
fun addRideRequest(request: ReceiveRideRequestResponse) { val updatedList = _rideRequests.value?.toMutableList() ?: mutableListOf() updatedList.add(request) _rideRequests.postValue(updatedList) startTimer(request.rideRequestId!!) } fun removeRideRequest(rideRequestId: String) { timerJobs[rideRequestId]?.cancel() timerJobs.remove(rideRequestId) val updatedList = _rideRequests.value?.filter { it.rideRequestId != rideRequestId }?.toMutableList() ?: mutableListOf() _rideRequests.postValue(updatedList) }
2. 切换到主线程执行Socket回调逻辑
Socket回调默认在子线程运行,直接操作ViewModel会导致UI更新异常,需切换到主线程:
socketService.listenToRideRequestEvent(object : SocketCallback<ReceiveRideRequestResponse> { override fun onListen(data: ReceiveRideRequestResponse) { Handler(Looper.getMainLooper()).post { openBottomSheet() rideRequestViewModel.addRideRequest(data) } } override fun onError(error: Throwable) { // Handle errors here if needed } })
3. 优化适配器更新,避免全量刷新
使用DiffUtil计算数据差异,只更新变化项,提升性能并避免UI闪烁:
- 创建DiffUtil回调类:
class RideRequestDiffCallback( private val oldList: List<ReceiveRideRequestResponse>, private val newList: List<ReceiveRideRequestResponse> ) : DiffUtil.Callback() { override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition].rideRequestId == newList[newItemPosition].rideRequestId } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition] == newList[newItemPosition] } }
- 修改适配器更新方法:
fun updateData(newData: List<ReceiveRideRequestResponse>) { val diffResult = DiffUtil.calculateDiff(RideRequestDiffCallback(dataList, newData)) this.dataList = newData diffResult.dispatchUpdatesTo(this) } fun updateTimers(newTimers: Map<String, Long>) { timerData.putAll(newTimers) // 仅更新计时器变化的项 dataList.forEachIndexed { index, item -> if (newTimers.containsKey(item.rideRequestId)) { notifyItemChanged(index) } } }
4. 降低计时器更新频率
当前每100ms更新一次LiveData过于频繁,改为每秒更新一次(匹配UI显示的秒数):
private fun startTimer(rideRequestId: String) { val timerDuration = 25000L // 25 seconds timerJobs[rideRequestId] = viewModelScope.launch { var remainingTime = timerDuration while (remainingTime > 0) { delay(1000L) remainingTime -= 1000L val updatedTimers = _timers.value.orEmpty().toMutableMap() updatedTimers[rideRequestId] = remainingTime _timers.postValue(updatedTimers) } removeRideRequest(rideRequestId) } }
5. 确保BottomSheet活跃时更新UI
如果openBottomSheet()存在延迟或BottomSheet未完全初始化,可在onStart方法中再次触发适配器更新,或确保调用addRideRequest时BottomSheet已处于可见状态。
内容的提问来源于stack exchange,提问作者Ghazanfar Ateeb
相关产品推荐
相关产品推荐

