Firebase Realtime Database首次调用addValueEventListener失效致崩溃
问题描述
我想获取Firebase数据库中子节点的数量,将该数量作为键以提升数据库可读性。但点击按钮插入新数据时,reference2.child(Rname).addValueEventListener方法未正常工作,导致ordercnt[0]为空,执行reference2.child(Rname).child(ordercnt[0]).child(Integer.toString(i)).setValue(order)时因子路径为空引发崩溃。
相关代码
Cart_submitBtn = view.findViewById(R.id.Cart_submitBtn); Cart_submitBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String Rname = selected.get(0).getRName(); reference2.child(Rname).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { if(snapshot.exists()){ ordercnt[0] = Long.toString(snapshot.getChildrenCount()); } else{ ordercnt[0] = "0"; } } @Override public void onCancelled(@NonNull DatabaseError error) { } }); reference1.child(mAuth.getCurrentUser().getUid()).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() { @Override public void onComplete(@NonNull Task<DataSnapshot> task) { User user = task.getResult().getValue(User.class); if(user != null){ for(int i = 0; i < selected.size(); i++) { String order = ""; order += "餐點名稱: " + selected.get(i).getsName() + " "; order += selected.get(i).getsCnt() + "份"; order += selected.get(i).getsPrice() + "元 "; reference2.child(Rname).child(ordercnt[0]).child(Integer.toString(i)).setValue(order); } } } }); } });
报错情况
- 触发
NullPointerException,提示"Can't pass null for argument 'pathString' in child()" - 崩溃栈指向调用
child(ordercnt[0])的代码行
解决方案
问题根源
Firebase数据库操作是异步执行的,addValueEventListener和get().addOnCompleteListener会在后台线程完成,主线程会直接继续执行后续代码,导致写入数据时ordercnt[0]还未被赋值,最终因空值引发崩溃。
修复代码
将数据写入逻辑嵌套到异步回调中,确保拿到子节点数量和用户信息后再执行写入:
Cart_submitBtn = view.findViewById(R.id.Cart_submitBtn); Cart_submitBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String Rname = selected.get(0).getRName(); // 先获取用户信息 reference1.child(mAuth.getCurrentUser().getUid()).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() { @Override public void onComplete(@NonNull Task<DataSnapshot> task) { User user = task.getResult().getValue(User.class); if(user != null){ // 获取用户信息后,一次性获取子节点数量(无需实时监听) reference2.child(Rname).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { String orderCount = snapshot.exists() ? Long.toString(snapshot.getChildrenCount()) : "0"; // 拿到计数后执行写入 for(int i = 0; i < selected.size(); i++) { String order = String.format("餐點名稱: %s %d份 %d元 ", selected.get(i).getsName(), selected.get(i).getsCnt(), selected.get(i).getsPrice()); reference2.child(Rname).child(orderCount).child(Integer.toString(i)).setValue(order); } } @Override public void onCancelled(@NonNull DatabaseError error) { // 添加错误日志,方便排查 Log.e("FirebaseError", "获取子节点数量失败: " + error.getMessage()); } }); } } }); } });
优化建议
- 替换监听方法:用
addListenerForSingleValueEvent代替addValueEventListener,仅获取一次数据,避免不必要的实时监听。 - 移除数组传值:直接在回调中使用局部变量传递计数,避免空指针风险。
- 优化字符串拼接:用
String.format替代多次+=,代码更简洁高效。 - 添加错误处理:在
onCancelled中记录错误日志,便于排查问题。 - 考虑自动生成键:如果仅需要唯一订单标识,推荐使用Firebase的
push()方法生成唯一键,避免多用户同时写入时的计数冲突。
内容的提问来源于stack exchange,提问作者jim900109
相关产品推荐
相关产品推荐

