Firebase实时数据库同步RecyclerView数据为空问题求助
问题:Firebase Realtime DB同步预约列表时RecyclerView为空,notifyDataSetChanged()未生效
已完成工作
创建了Reservation类、ReservationAdapter及专用布局item_customer_reservation.xml,测试阶段用固定数据填充列表项。
ReservationAdapter代码
class ReservationAdapter(private val reservationList: Array<Reservation>) : RecyclerView.Adapter<ReservationAdapter.ReservationViewHolder>() { inner class ReservationViewHolder(private val binding: ItemCustomerReservationBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(reservation: Reservation) { Log.w(Constants.CUTITAPP, "ReservationViewHolder:onBind") binding.apply { // TODO dummy informations tvSalonName.text = "test salon" tvServiceName.text = "test name" tvServiceDate.text = "test date" } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReservationViewHolder { val binding = ItemCustomerReservationBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ReservationViewHolder(binding) } override fun onBindViewHolder(reservationViewHolder: ReservationViewHolder, position: Int) { reservationViewHolder.bind(reservationList[position]) } override fun getItemCount() = reservationList.size }
承载RecyclerView的Fragment代码
class CustomerHomeFragment : Fragment() { private lateinit var db: DatabaseReference private var uid: String? = null private var reservationList: Array<Reservation>? = null private var reservationAdapter: ReservationAdapter? = null // view binding private var _binding: FragmentCustomerHomeBinding? = null private val binding get() = _binding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { uid = it.getString(Constants.UID) // get the UID from the passed parameter } db = FirebaseDatabase.getInstance().reference reservationList = arrayOf() reservationAdapter = ReservationAdapter(reservationList!!) } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { // inflate the layout for this fragment _binding = FragmentCustomerHomeBinding.inflate(inflater, container, false) addCustomerReservationsEventListener() binding.apply { rvReservations.adapter = reservationAdapter } return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null // needed to destroy the binding when the fragment is destroyed } /** * Adds a [ValueEventListener] for the reservations of the customer. * Populates the reservationList with the [Reservation] of the customer and notifies the adapter. */ private fun addCustomerReservationsEventListener() { val reservationsReference = db.child(Constants.DB_CUSTOMERS).child(uid!!).child(Constants.DB_CUSTOMER_RESERVATIONS) val customerReservationsListener = object : ValueEventListener { override fun onDataChange(reservationUidListSnapshot: DataSnapshot) { // get reservationUidList val reservationUidList = reservationUidListSnapshot.getValue<HashMap<String, String>>() Log.d(Constants.CUTITAPP, "reservationUidList: $reservationUidList") if (!reservationUidList.isNullOrEmpty()) { // get a Reservation for each Uid to populate the list for (reservationUid in reservationUidList.values) { if (reservationUid.isNotEmpty()) // TODO temporary fix { db.child(Constants.DB_RESERVATIONS).child(reservationUid).get().addOnSuccessListener { reservationSnapshot: DataSnapshot -> val reservation = reservationSnapshot.getValue<Reservation>() reservationList = reservationList?.plus(reservation!!) reservationAdapter?.notifyDataSetChanged() Log.d(Constants.CUTITAPP, "reservationList size: ${reservationList?.size}") Log.d(Constants.CUTITAPP, "reservationAdapter size: ${reservationAdapter?.itemCount}") } } } } } override fun onCancelled(databaseError: DatabaseError) { Log.w(getString(R.string.app_name), "CustomerHomeFragment:onCancelled", databaseError.toException()) } } reservationsReference.addValueEventListener(customerReservationsListener) } companion object { @JvmStatic fun newInstance(uid: String) = CustomerHomeFragment().apply { arguments = Bundle().apply { putString(Constants.UID, uid) // pass the parameters to the created Fragment } } } }
问题描述
reservationList已正确获取到用户的1条预约数据(数据库中仅添加了1条测试数据),但reservationAdapter始终显示0条数据,notifyDataSetChanged()似乎未生效,最终导致RecyclerView为空。
解决方案
核心问题分析
- 数组不可变性:Kotlin的
Array是不可变集合,通过reservationList?.plus(reservation!!)生成的是新数组,适配器持有的还是最初的空数组引用,无法感知到新数据。 - 异步请求顺序混乱:单个预约的
get()请求是异步操作,多次调用notifyDataSetChanged()会导致重复通知,且可能出现数据未完全加载就通知的情况。
具体修复步骤
1. 替换为可变集合存储数据
将reservationList的类型从Array<Reservation>?改为MutableList<Reservation>,这样可以直接修改原集合,适配器能实时感知内容变化:
// Fragment中的变量声明 private var reservationList: MutableList<Reservation> = mutableListOf() // onCreate中初始化 reservationList = mutableListOf() reservationAdapter = ReservationAdapter(reservationList)
2. 调整适配器适配可变集合
修改ReservationAdapter的构造参数为可变列表,确保适配器持有集合的实时引用:
class ReservationAdapter(private val reservationList: MutableList<Reservation>) : RecyclerView.Adapter<ReservationAdapter.ReservationViewHolder>() { // 其他代码保持不变 override fun getItemCount() = reservationList.size }
3. 优化数据加载与通知逻辑
在onDataChange中先清空旧数据,批量完成所有异步请求后再统一更新列表并通知适配器:
override fun onDataChange(reservationUidListSnapshot: DataSnapshot) { val reservationUidList = reservationUidListSnapshot.getValue<HashMap<String, String>>() Log.d(Constants.CUTITAPP, "reservationUidList: $reservationUidList") // 清空旧数据 reservationList.clear() if (!reservationUidList.isNullOrEmpty()) { val fetchTasks = mutableListOf<Task<DataSnapshot>>() // 收集所有异步请求任务 for (reservationUid in reservationUidList.values) { if (reservationUid.isNotEmpty()) { val task = db.child(Constants.DB_RESERVATIONS).child(reservationUid).get() fetchTasks.add(task) } } // 等待所有请求完成后统一处理 Tasks.whenAllComplete(fetchTasks).addOnSuccessListener { tasks -> for (task in tasks) { if (task.isSuccessful) { val reservationSnapshot = task.result as DataSnapshot val reservation = reservationSnapshot.getValue<Reservation>() reservation?.let { reservationList.add(it) } } } // 统一通知适配器更新 reservationAdapter?.notifyDataSetChanged() Log.d(Constants.CUTITAPP, "reservationList size: ${reservationList.size}") Log.d(Constants.CUTITAPP, "reservationAdapter size: ${reservationAdapter?.itemCount}") } } else { // 无数据时也通知适配器更新 reservationAdapter?.notifyDataSetChanged() } }
4. 可选:用DiffUtil优化更新效率
如果后续数据量较大,推荐使用DiffUtil替代notifyDataSetChanged(),只更新变化的列表项,提升RecyclerView性能:
// 在ReservationAdapter中添加更新方法 fun updateData(newList: List<Reservation>) { val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() { override fun getOldListSize() = reservationList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 根据Reservation的唯一ID判断是否为同一项目 return reservationList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { // 判断内容是否一致 return reservationList[oldItemPosition] == newList[newItemPosition] } }) reservationList.clear() reservationList.addAll(newList) diffResult.dispatchUpdatesTo(this) }
在Fragment中调用该方法替换notifyDataSetChanged():
reservationAdapter?.updateData(reservationList)
内容的提问来源于stack exchange,提问作者mik3.rizzo
相关产品推荐
相关产品推荐

