如何通过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
相关产品推荐
相关产品推荐

