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

从Firebase实时数据库删除数据时触发IndexOutOfBoundsException异常

Firebase RecyclerView 删除食材时索引异常问题解决

问题背景

Android应用通过RecyclerView展示Firebase Realtime Database中的用户食材,为列表项添加删除功能,使用代码fridgeRef.child(getRef(position).getKey()).removeValue();执行删除操作。

Firebase JSON结构:

{
  "Ingredient": {
    "hgO9joLhmfh4Wjn7xYpyqcYmNOB3": {
      "Cashews": {
        "Expiry": "2023-01-21",
        "Ingredient": 1
      },
      "Macadamia Nuts": {
        "Expiry": "2022-11-22",
        "Ingredient": 1
      },
      "Pecans": {
        "Expiry": "2023-01-21",
        "Ingredient": 1
      },
      "Pine Nuts": {
        "Expiry": "2022-11-22",
        "Ingredient": 1
      },
      "Pistachios": {
        "Expiry": "2023-01-21",
        "Ingredient": 1
      }
    }
  }
}

问题表现

  • 删除第一个项Cashews成功,但删除Macadamia Nuts时会误删下方的Pecans;
  • 仅剩Macadamia Nuts时,删除操作抛出索引越界异常导致崩溃:

java.lang.IndexOutOfBoundsException: Index: 4, Size: 3
at java.util.ArrayList.get(ArrayList.java:437)
at com.firebase.ui.common.BaseObservableSnapshotArray.getSnapshot(BaseObservableSnapshotArray.java:70)
at com.firebase.ui.database.FirebaseRecyclerAdapter.getRef(FirebaseRecyclerAdapter.java:114)
at com.example.recipely.fridge$2.lambda$onBindViewHolder$0$com-example-recipely-fridge$2(fridge.java:163)
at com.example.recipely.fridge$2$$ExternalSyntheticLambda0.onClick(Unknown Source:4)

相关代码(fridge.java)

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    ingredientView = inflater.inflate(R.layout.fragment_fridge, container, false);

    expiryNote = (LinearLayout) ingredientView.findViewById(R.id.expiryNote);
    HaveData = (ScrollView) ingredientView.findViewById(R.id.HaveData);
    noFridge = (LinearLayout) ingredientView.findViewById(R.id.noFridge);

    expiryNote.setVisibility(View.GONE);
    HaveData.setVisibility(View.GONE);
    myIngredientList = (RecyclerView) ingredientView.findViewById(R.id.ingredientList);
    myIngredientList.setLayoutManager(new LinearLayoutManager(getContext()));

    mAuth = FirebaseAuth.getInstance();
    currentUserID = mAuth.getCurrentUser().getUid();

    fridgeRef = FirebaseDatabase.getInstance().getReference().child("Ingredient").child(currentUserID);

    fridgeRef.addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(@NonNull DataSnapshot snapshot) {
            if(snapshot.exists()){
                HaveData.setVisibility(View.VISIBLE);
            }
        }

        @Override
        public void onCancelled(@NonNull DatabaseError error) {

        }
    });

    return ingredientView;
}

@Override
public void onStart() {
    super.onStart();


    FirebaseRecyclerOptions<fridgeItem> options =
            new FirebaseRecyclerOptions.Builder<fridgeItem>()
                    .setQuery(fridgeRef , fridgeItem.class)
                    .build();

    FirebaseRecyclerAdapter<fridgeItem, fridgeViewHolder> adapter
            = new FirebaseRecyclerAdapter<fridgeItem, fridgeViewHolder>(options) {
        @Override
        protected void onBindViewHolder(@NonNull fridgeViewHolder holder, int position, @NonNull fridgeItem model) {
            String itemName = getRef(position).getKey();

            LocalDate expiry = LocalDate.parse(model.getExpiry());
            LocalDate today = LocalDate.now();
            long dayDiff = DAYS.between(today, expiry);

            if(dayDiff <= 7){
                holder.cardViewIngredient.setBackgroundColor(Color.parseColor("#FFD1D1"));
                expiryNote.setVisibility(View.VISIBLE);
            }
            holder.ingredName.setText(itemName);
            holder.ingredExpiry.setText(dayDiff + " Days until expiry");

            holder.deleteBtn.setOnClickListener(v ->{
                String output = fridgeRef.child(getRef(position).getKey()).toString();
                Log.d("Fridge" , output );
                fridgeRef.child(getRef(position).getKey()).removeValue();
            });
        }

        @NonNull
        @Override
        public fridgeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.ingredientrecycle, parent, false);
            fridgeViewHolder viewHolder = new fridgeViewHolder(view);
            return viewHolder;
        }
    };

    myIngredientList.setAdapter(adapter);
    adapter.startListening();
}

public static class fridgeViewHolder extends RecyclerView.ViewHolder{

    TextView ingredName, ingredExpiry;
    LinearLayout deleteBtn, cardViewIngredient;

    public fridgeViewHolder(@NonNull View itemView) {
        super(itemView);


        ingredName = itemView.findViewById(R.id.itemName);
        ingredExpiry = itemView.findViewById(R.id.itemExpiry);

        cardViewIngredient = itemView.findViewById(R.id.cardViewIngredient);
        //set button to delete an item
        deleteBtn = itemView.findViewById(R.id.deleteItem);
    }
}
}

问题原因

删除按钮点击事件中直接使用了onBindViewHolder传入的position参数。RecyclerView的position是动态变化的:当列表项被删除后,Adapter会更新数据集,但之前绑定的点击事件中保留的是旧的索引值,导致后续删除时获取错误的getRef(position),进而误删数据或触发索引越界异常。

解决方案

方案一:使用getAdapterPosition()获取实时位置

修改删除按钮的点击事件,通过ViewHolder获取当前最新的有效位置:

holder.deleteBtn.setOnClickListener(v ->{
    int currentPosition = holder.getAdapterPosition();
    if(currentPosition != RecyclerView.NO_POSITION){ // 确保位置有效
        String itemKey = getRef(currentPosition).getKey();
        fridgeRef.child(itemKey).removeValue();
    }
});

方案二:提前保存食材Key到ViewHolder Tag

在绑定视图时,将当前食材的Key保存到ViewHolder的Tag中,点击时直接取出使用,完全脱离对position的依赖:

// 在onBindViewHolder方法中添加
String itemKey = getRef(position).getKey();
holder.itemView.setTag(itemKey);

// 修改删除点击事件
holder.deleteBtn.setOnClickListener(v ->{
    String targetKey = (String) holder.itemView.getTag();
    fridgeRef.child(targetKey).removeValue();
});

额外优化:过期提示的动态控制

原代码中每次绑定过期食材都会显示expiryNote,但所有过期食材被删除后无法自动隐藏。可以在Adapter中重写onDataChanged方法,统一检查数据集状态:

@Override
public void onDataChanged() {
    super.onDataChanged();
    boolean hasExpiredItem = false;
    for(int i = 0; i < getItemCount(); i++){
        fridgeItem item = getItem(i);
        LocalDate expiry = LocalDate.parse(item.getExpiry());
        long dayDiff = DAYS.between(LocalDate.now(), expiry);
        if(dayDiff <= 7){
            hasExpiredItem = true;
            break;
        }
    }
    expiryNote.setVisibility(hasExpiredItem ? View.VISIBLE : View.GONE);
}

内容的提问来源于stack exchange,提问作者Muhammad Zarith

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:54:22