I have a BottomSheetDialogFragment containing a RecyclerView in my Android app. The data for the RecyclerView is being received one by one from a socket connection. I need to add these items to the RecyclerView dynamically as they come in and set a timer on each item using a ViewModel.
The main issue I'm encountering is that the RecyclerView items are not getting updated in the UI when new data is added. The changes only reflect when the state of the fragment or the RecyclerView is changed. I am using a ViewModel to manage the data and timer, and the socket is running asynchronously, but the UI updates are delayed or not showing until the state changes.
I've tried notifying the adapter using notifyItemInserted() and other common methods, but the problem persists.
Steps I've followed:
- Data is received via the socket one by one.
- Data is added to the list in the ViewModel.
- The RecyclerView adapter is updated accordingly.
- I attempt to set a timer for each item as it is added.
Any help or suggestions on how to ensure the RecyclerView updates in real time when new data is added would be appreciated.
This is my code:
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)
}
}
This is how I am getting data from Socket and setting it up on BottomSheetDialogFragment
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()
}
}
}