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

如何通过ChildEventListener结合ListAdapter实时更新Firebase数据?

问题描述

开发项目时,从Firebase Realtime Database获取数据并通过RecyclerView的ListAdapter展示对象列表,Firebase控制台修改数据后,仅在Activity重建时列表才刷新,无法实时更新。已确认数据变更时onChildChanged触发,但列表未同步更新。使用ListAdapter是为了避免notifyDatasetChanged()的性能开销。

初始化数据与监听代码:

//initialise data
database = FirebaseDatabase.getInstance()
categoryList = arrayListOf()

//set up adapter and recycler view
val rvCategory: RecyclerView = binding.rvCategory
val categoryAdapter = CategoryAdapter()
rvCategory.layoutManager = LinearLayoutManager(this@CategoryListActivity)
rvCategory.adapter = categoryAdapter
val databaseReference = database.getReference("category")
databaseReference.addChildEventListener(object: ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
        //adding the data in list
        val data = snapshot.getValue(Category::class.java)
        categoryList.add(data!!)
        //sends the data to the recycler view
        categoryAdapter.submitList(categoryList)
    }

    override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
        //adding the data in list
        val data = snapshot.getValue(Category::class.java)
        categoryList.add(data!!)
        //sends the data to the recycler view
        categoryAdapter.submitList(categoryList)
    }

    override fun onChildRemoved(snapshot: DataSnapshot) {
        //adding the data in list
        val data = snapshot.getValue(Category::class.java)
        categoryList.add(data!!)
        //sends the data to the recycler view
        categoryAdapter.submitList(categoryList)
    }

    override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
        //adding the data in list
        val data = snapshot.getValue(Category::class.java)
        categoryList.add(data!!)
        //sends the data to the recycler view
        categoryAdapter.submitList(categoryList)
    }

    override fun onCancelled(error: DatabaseError) {
        Toast.makeText(this@CategoryListActivity, error.message,Toast.LENGTH_SHORT).show()
    }
})

分类列表适配器代码:

class CategoryAdapter: ListAdapter<Category, CategoryAdapter.CategoryViewHolder>(DiffCallBack) {
    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int
    ): CategoryViewHolder {
        return CategoryViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_category_list,parent,false )
        )
    }

    override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
        holder.bind(getItem(position))
        holder.itemView.setOnClickListener {
            val intent = Intent(holder.itemView.context,CategoryDoctorListActivity::class.java)
            val bundle = Bundle()
            bundle.putString("categoryName",getItem(position).cName)
            intent.putExtras(bundle)
            startActivity(holder.itemView.context,intent,bundle)
        }
    }

    class CategoryViewHolder(view: View) : RecyclerView.ViewHolder(view){
        private val categoryName: TextView = view.findViewById(R.id.category_name)
        private val categoryImage: ImageView = view.findViewById(R.id.category_image)

        fun bind(categoryData: Category){
            categoryName.text = categoryData.cName
            Glide.with(itemView.context).load(categoryData.cPhoto).into(categoryImage)
        }
    }

    companion object{
        private val DiffCallBack = object: DiffUtil.ItemCallback<Category>(){
            override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean {
                return oldItem.cId == newItem.cId
            }

            override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean {
                return areItemsTheSame(oldItem,newItem)
            }

        }
    }

}

解决方案

你的代码存在三个核心问题,逐一修复即可实现实时更新:

1. 修正ChildEventListener回调逻辑

当前所有回调(包括onChildChanged、onChildRemoved)都执行categoryList.add(data!!),完全不符合数据变更的逻辑,需根据回调类型处理数据:

  • onChildAdded:按Firebase返回顺序插入新数据
  • onChildChanged:找到对应ID的项并更新内容
  • onChildRemoved:删除对应ID的项
  • onChildMoved:调整对应项的位置

修改后的监听代码:

databaseReference.addChildEventListener(object: ChildEventListener {
    override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
        val data = snapshot.getValue(Category::class.java) ?: return
        // 按Firebase返回的顺序插入数据
        val index = previousChildName?.let { prevName ->
            categoryList.indexOfFirst { it.cName == prevName } + 1
        } ?: 0
        categoryList.add(index, data)
        categoryAdapter.submitList(ArrayList(categoryList))
    }

    override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
        val data = snapshot.getValue(Category::class.java) ?: return
        val index = categoryList.indexOfFirst { it.cId == data.cId }
        if (index != -1) {
            categoryList[index] = data
            categoryAdapter.submitList(ArrayList(categoryList))
        }
    }

    override fun onChildRemoved(snapshot: DataSnapshot) {
        val data = snapshot.getValue(Category::class.java) ?: return
        categoryList.removeAll { it.cId == data.cId }
        categoryAdapter.submitList(ArrayList(categoryList))
    }

    override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
        val data = snapshot.getValue(Category::class.java) ?: return
        // 先移除旧位置的项
        categoryList.removeAll { it.cId == data.cId }
        // 插入到新位置
        val index = previousChildName?.let { prevName ->
            categoryList.indexOfFirst { it.cName == prevName } + 1
        } ?: 0
        categoryList.add(index, data)
        categoryAdapter.submitList(ArrayList(categoryList))
    }

    override fun onCancelled(error: DatabaseError) {
        Toast.makeText(this@CategoryListActivity, error.message,Toast.LENGTH_SHORT).show()
    }
})

2. 提交新的列表实例给ListAdapter

ListAdapter的submitList方法依赖新旧列表的差异对比,如果一直传入同一个categoryList对象,ListAdapter会判定列表无变化,不会触发更新。每次提交时需传入列表的新副本,比如ArrayList(categoryList)。

3. 修复DiffUtil内容对比逻辑

当前areContentsTheSame直接复用areItemsTheSame的结果,导致数据内容(如名称、图片)变化时,DiffUtil判定内容未改变,不会更新UI。需对比Category对象的所有展示字段:

companion object{
    private val DiffCallBack = object: DiffUtil.ItemCallback<Category>(){
        override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean {
            return oldItem.cId == newItem.cId
        }

        override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean {
            // 对比所有需要展示的字段
            return oldItem.cId == newItem.cId &&
                   oldItem.cName == newItem.cName &&
                   oldItem.cPhoto == newItem.cPhoto
        }
    }
}

内容的提问来源于stack exchange,提问作者Rishabh Aggarwal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 15:55:08