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

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为空。


解决方案

核心问题分析

  1. 数组不可变性:Kotlin的Array是不可变集合,通过reservationList?.plus(reservation!!)生成的是新数组,适配器持有的还是最初的空数组引用,无法感知到新数据。
  2. 异步请求顺序混乱:单个预约的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 22:54:18