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

Android RecyclerView删除购物车Item崩溃问题排查求助

Android RecyclerView购物车删除Item崩溃问题(IndexOutOfBoundsException)

异常信息

FATAL EXCEPTION: main
Process: com.example.hungryhopper, PID: 8373
java.lang.IndexOutOfBoundsException: Index 1 out of bounds for length 0

问题描述

在Android应用中使用RecyclerView展示Firebase存储的购物车数据,删除Item时数据已从购物车移除,但APP随即崩溃抛出上述索引越界异常。已确认列表初始化正常,日志验证position和列表均有有效值,商品数量修改功能正常。

相关代码

class CartAdapter(
    private val cartItems: MutableList<String>,
    private val cartItemsPrice: MutableList<String>,
    private val cartDescriptions: MutableList<String>,
    private val cartImages: MutableList<String>,
    private val cartQuantity: MutableList<Int>,
    private val cartIngredients: MutableList<String>,
    private val context: Context,
    private val onDeleteClickListener: (position: Int) -> Unit
) : RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
    private val auth = FirebaseAuth.getInstance()
    init {
        val database = FirebaseDatabase.getInstance()
        val userId = auth.currentUser?.uid ?: ""
        val cartItemNumber = cartItems.size
        itemQuantities = IntArray(cartItemNumber) { 1 }
        cartItemsReference =database.reference.child(USER_NODE).child(userId).child(CARTITEM_NODE)
    }
    companion object {
        private var itemQuantities = intArrayOf()
        private lateinit var cartItemsReference: DatabaseReference
    }
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CartViewHolder {
        return CartViewHolder(
            CartItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }
    override fun getItemCount(): Int {
        return cartItems.size
    }
    override fun onBindViewHolder(holder: CartViewHolder, position: Int) {
        holder.bind(position)
    }
    inner class CartViewHolder(private val binding: CartItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(position: Int) {
            binding.apply {
                cartFoodName.text = cartItems[position]
                cartItemPrice.text = cartItemsPrice[position]
                quantity.text = itemQuantities[position].toString()
                val uriString = cartImages[position]
                val uri = Uri.parse(uriString.toString())
                Glide.with(context).load(uri).into(cartImage)
                minusButton.setOnClickListener {
                    decreaseQuantity(position)
                }
                plusButton.setOnClickListener {
                    increaseQuantity(position)
                }
                deleteButton.setOnClickListener {
                    deleteItem(position)
                }
            }
        }
        private fun decreaseQuantity(position: Int) {
            if (itemQuantities[position] > 1) {
                itemQuantities[position]--
                cartQuantity[position] = itemQuantities[position]
                binding.quantity.text = itemQuantities[position].toString()
            }
        }
        private fun increaseQuantity(position: Int) {
            if (itemQuantities[position] < 10) {
                itemQuantities[position]++
                cartQuantity[position] = itemQuantities[position]
                binding.quantity.text = itemQuantities[position].toString()
            }
        }
        private fun deleteItem(position: Int) {
            val positionRetrieve = position
            getUniqueAtPosition(positionRetrieve) { uniqueKey ->
                if (uniqueKey != null) {
                    removeItem(position, uniqueKey)
                }
            }
        }
    }
    private fun removeItem(position: Int, uniqueKey: String) {
        cartItemsReference.child(uniqueKey).removeValue().addOnSuccessListener {
            cartItems.removeAt(position)
            cartImages.removeAt(position)
            cartDescriptions.removeAt(position)
            cartQuantity.removeAt(position)
            cartItemsPrice.removeAt(position)
            cartIngredients.removeAt(position)
            itemQuantities = itemQuantities.filterIndexed { index, i -> index != position  }.toIntArray()
            notifyItemRemoved(position)
            notifyItemRangeChanged(position, cartItems.size)
            Toast.makeText(context, " Item Deleted", Toast.LENGTH_SHORT).show()
        }.addOnFailureListener {
            Toast.makeText(context, "Failed to Delete", Toast.LENGTH_SHORT).show()
        }
    }
    private fun getUniqueAtPosition(i: Int, onComplete: (String?) -> Unit) {
        cartItemsReference.addListenerForSingleValueEvent(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                var uniqueKey: String? = null
                snapshot.children.forEachIndexed { index, dataSnapshot ->
                    if (index == i) {
                        uniqueKey = dataSnapshot.key
                        return@forEachIndexed
                    }
                }
                onComplete(uniqueKey)
            }
            override fun onCancelled(error: DatabaseError) {}
        })
    }

    fun getUpdatedItemQuantities(): MutableList<Int> {
        val itemQuantity = mutableListOf<Int>()
        itemQuantity.addAll(cartQuantity)
        return itemQuantity
    }
}

问题根源与修复方案

1. 静态变量引发的共享冲突

companion object中的itemQuantities是全局静态变量,多个Adapter实例会共享该数组,删除操作后数组长度与本地列表不一致,导致索引越界。

修复:将itemQuantities和cartItemsReference改为Adapter实例变量,移除companion object中的定义:

class CartAdapter(
    // 原有参数
) : RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
    private val auth = FirebaseAuth.getInstance()
    private var itemQuantities = intArrayOf()
    private lateinit var cartItemsReference: DatabaseReference

    init {
        val database = FirebaseDatabase.getInstance()
        val userId = auth.currentUser?.uid ?: ""
        val cartItemNumber = cartItems.size
        itemQuantities = IntArray(cartItemNumber) { 1 }
        cartItemsReference = database.reference.child(USER_NODE).child(userId).child(CARTITEM_NODE)
    }

    // 移除原companion object
}

2. 异步回调中使用过时position

getUniqueAtPosition是异步操作,若用户连续删除Item,回调触发时原position对应的列表项可能已被移除,导致索引越界。

修复:在回调前后检查position有效性:

private fun deleteItem(position: Int) {
    if (position < 0 || position >= cartItems.size) return
    val currentPosition = position
    getUniqueAtPosition(currentPosition) { uniqueKey ->
        uniqueKey?.let {
            if (currentPosition < cartItems.size) {
                removeItem(currentPosition, it)
            }
        }
    }
}

3. Firebase节点匹配方式不可靠

通过forEachIndexed按position匹配Firebase节点key的方式不严谨,Firebase节点顺序可能与本地列表不一致,导致删除错误节点,进而引发数据不同步。

修复:加载购物车数据时,将Firebase节点key与商品数据绑定(建议创建CartItem数据类),删除时直接使用存储的key:

// 定义数据类
data class CartItem(
    val id: String, // Firebase节点key
    val name: String,
    val price: String,
    val description: String,
    val imageUrl: String,
    var quantity: Int,
    val ingredients: String
)

// 修改Adapter参数为单个CartItem列表
class CartAdapter(
    private val cartItems: MutableList<CartItem>,
    private val context: Context,
    private val onDeleteClickListener: (position: Int) -> Unit
) : RecyclerView.Adapter<CartAdapter.CartViewHolder>() {
    // 内部逻辑对应修改,比如bind时直接调用cartItems[position].name
    private fun deleteItem(position: Int) {
        if (position < 0 || position >= cartItems.size) return
        val targetItem = cartItems[position]
        cartItemsReference.child(targetItem.id).removeValue().addOnSuccessListener {
            cartItems.removeAt(position)
            itemQuantities = itemQuantities.filterIndexed { index, _ -> index != position }.toIntArray()
            notifyItemRemoved(position)
            notifyItemRangeChanged(position, cartItems.size)
            Toast.makeText(context, "Item Deleted", Toast.LENGTH_SHORT).show()
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:26:01