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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:49:50