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

使用FirebaseRecyclerAdapter读取复杂嵌套Firebase实时数据库的POJO构建问题

Firebase数据结构截图

问题描述

userId下的首个数据节点存储链表类型数据,每个索引包含data和info两部分。目前的困惑点在于索引data下的唯一动态键-MkqHbtLzqzI...,每个索引data下都有多个这类动态生成的不同键,其中Matrix是List<Float>类型。

我们需要使用FirebaseRecyclerAdapter,该组件的setQuery方法要求指定映射的类类型,但不清楚如何为上述结构定义对应的POJO类(Kotlin中为data class)。
需要加载到RecyclerView的对象从2这个索引开始。截图中6154...是userId,其下有一个名为data的节点,该节点存储了linkedList<Long>格式的条目列表,需要将这些条目展示在recyclerView中。
尝试编写了如下data classes,但存在的问题是这些类既无法获取到索引值(截图中的2),也无法获取到动态唯一键(-MkqH...)。

现有POJO定义

UserData(用于firebaseRecyclerViewAdapter的类)

@Keep
@IgnoreExtraProperties
data class UserData(
    var data: UserPrefsData? = null,
    var info: UserPrefsInfo? = null
) {
    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "data" to data,
            "info" to info
        )
    }
}

UserPrefsData

@Keep
@IgnoreExtraProperties
data class UserPrefsData(
    var color: String = BRUSH_BLACK,
    var stroke: Float = 8f,
    var data: String = "",
    var shape: Shape? = null,
    var fill: Boolean = false,
    var matrix : ArrayList<Float>? = null
) {
    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "color" to color,
            "stroke" to stroke,
            "data" to data,
            "shape" to shape,
            "fill" to fill,
            "matrix" to matrix
        )
    }
}

UserPrefsInfo

@Keep
@IgnoreExtraProperties
data class PageInfo(
     var color: String = BRUSH_WHITE,
    var backGround: BackGround = BackGround.COLOR,
    var orientation: Int = ORIENTATION_PORTRAIT,
    var previous: Int = -1,
    var next: Int = -1
) {
    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "color" to color,
            "backGround" to backGround,
            "orientation" to orientation,
            "previous" to previous,
            "next" to next
        )
    }
}

Activity代码

private fun initFirebaseAdapter(): FirebaseAdapter? {
        val userDataRef = databaseReference.child("data")
        val options = FirebaseRecyclerOptions.Builder<UserData>()
            .setLifecycleOwner(this)
            .setQuery(userDataRef, UserData::class.java)
            .build()
        return FirebaseAdapter(
            options,
            isAdmin,
            rvItemEventListener
        )
    }
private fun setRecyclerViewAdapter(
        recyclerView: RecyclerView?,
        recyclerViewAdapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>?
    ) {
        recyclerView?.adapter = recyclerViewAdapter
    }
rvFirebaseAdapter = initFirebaseAdapter()
setRecyclerViewAdapter(binding.idVRv, rvFirebaseAdapter)

FirebaseAdapter代码

class FirebaseAdapter(var options: FirebaseRecyclerOptions<UserData>,
                                val showDeleteOption: Boolean,
                                private val callbackListener: Callbacks.RecyclerViewItemCallback):
    FirebaseRecyclerAdapter<UserData, FirebaseAdapter.UserViewHolder>(options) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val itemView =
            LayoutInflater.from(parent.context).inflate(R.layout.user_item, parent, false)
        val binding = UserItemBinding.bind(itemView)
        return UserViewHolder(binding, itemView)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onBindViewHolder: position: $position item: ${getItem(position)} _getting data here - it is working_")
        if (position != RecyclerView.NO_POSITION) {
            bindData(holder, position)
        }
    }

    private fun bindData(holder: UserViewHolder, position: Int) {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :bindData: position: $position" _This is also working_)
        holder.binding.idVTvPageNumber.text = (position + 1).toString()
    }

    override fun getItemCount(): Int {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :superGetItemCount: ${super.getItemCount()}")
        return super.getItemCount()
    }

    inner class UserViewHolder(val binding: UserItemBinding, itemView: View): RecyclerView.ViewHolder(itemView) {
        init {
            if (showDeleteOption) {
                binding.idVIvDelete.setOnClickListener {
                    Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: UserViewHolder: :bindingAdapterPosition: $bindingAdapterPosition absoluteAdapterPosition: $absoluteAdapterPosition" _This is working_)
                    onDeleteItem(adapterPosition, binding.idVIvDelete)
                }
            } else {
                binding.idVIvDelete.visibility = View.GONE
            }
        }
    }

    private fun onDeleteItem(adapterPosition: Int, idVIvDelete: View) {
        if (adapterPosition != RecyclerView.NO_POSITION) {
            Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onDeleteItem: position: $adapterPosition" _This is also working_)
            val item = getItem(adapterPosition)
            getRef(adapterPosition).removeValue()
            callbackListener.onDeleteItem(adapterPosition, item, idVIvDelete, itemCount)
            notifyItemRemoved(adapterPosition)
        }
    }

    override fun onBindViewHolder(
        holder: UserViewHolder,
        position: Int,
        model: UserData
    ) {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onBindViewHolder: firebase: position: $position UserData: $model" _This is never being called_)
        if (position != RecyclerView.NO_POSITION) {
            bindData(holder, position)
        }
    }

    override fun onChildChanged(
        type: ChangeEventType,
        snapshot: DataSnapshot,
        newIndex: Int,
        oldIndex: Int
    ) {
        super.onChildChanged(type, snapshot, newIndex, oldIndex)
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onChildChanged: snapshot: $snapshot" _This is working_)
    }

    override fun onDataChanged() {
        super.onDataChanged()
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onDataChanged: ")
    }

    override fun onError(error: DatabaseError) {
        super.onError(error)
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :onError: $error")
    }

    override fun getItem(position: Int): UserData {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :getItem: position: $position")
        return super.getItem(position)
    }

    override fun getRef(position: Int): DatabaseReference {
        Timber.d(" :$LOG_APP_NAME: FirebaseAdapter: :getRef: position: $position")
        return super.getRef(position)
    }
}

2021年10月6日更新

默认的onBindViewHolder会调用FirebaseRecyclerAdapter的onBindViewHolder,之前的实现没有加super调用,因此删除onBindViewHolder(holder: UserViewHolder, position: Int)方法后,onBindViewHolder(holder: UserViewHolder, position: Int, model: UserData)可以正常被触发,但仍然不知道如何获取上述索引值和动态唯一键。


解决方案

1. 核心逻辑说明

索引值和动态键不需要在POJO类中定义,Firebase SDK本身提供了对应的获取方式:

  • 外层的数字索引(截图中的2):在适配器中调用getRef(position).key即可直接拿到该节点的键值
  • data下的动态唯一键:将UserData类中data字段的类型改为Map<String, UserPrefsData>,Map的key就是-MkqH...这类动态生成的唯一键,value是对应的数据实体

2. 调整POJO定义

@Keep
@IgnoreExtraProperties
data class UserData(
    // 改为Map存储动态键和对应的数据实体
    var data: Map<String, UserPrefsData>? = null,
    // 修正命名,原有类名为PageInfo不是UserPrefsInfo
    var info: PageInfo? = null
) {
    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "data" to data,
            "info" to info
        )
    }
}

其余UserPrefsData和PageInfo类无需调整。

3. 调整查询范围(从索引2开始加载)

private fun initFirebaseAdapter(): FirebaseAdapter? {
    val userDataRef = databaseReference.child("data")
    // 按key排序,从值为2的节点开始查询
    val query = userDataRef.orderByKey().startAt("2")
    val options = FirebaseRecyclerOptions.Builder<UserData>()
        .setLifecycleOwner(this)
        .setQuery(query, UserData::class.java)
        .build()
    return FirebaseAdapter(
        options,
        isAdmin,
        rvItemEventListener
    )
}

4. 适配器中取值示例

override fun onBindViewHolder(holder: UserViewHolder, position: Int, model: UserData) {
    // 拿到外层数字索引值
    val indexKey = getRef(position).key
    // 遍历拿到data下所有动态键和对应的数据
    model.data?.forEach { (dynamicKey, prefsData) ->
        // dynamicKey就是-MkqH...这类动态唯一键
        val matrix = prefsData.matrix
        // 后续绑定视图的业务逻辑
    }
    // 拿info字段的方式和原有逻辑一致
    val pageInfo = model.info
}

内容的提问来源于stack exchange,提问作者Sagar Patel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 15:27:02