BottomSheetDialogFragment更新Firebase Realtime Database重复扣款问题排查
问题描述
实现了基于BottomSheetDialogFragment的底部弹窗功能,通过MaterialButtonToggleGroup组件供用户选择扣款金额,点击加入按钮后将更新Firebase Realtime Database中对应用户的钱包余额,扣除所选的对应金额。当前遇到异常:用户选择10₹并点击加入按钮后,代码未编写任何循环逻辑,却会持续从用户钱包中扣款直到余额为0,无法定位问题根源。
相关代码
BottomRedFragment.java
public class BottomRedFragment extends BottomSheetDialogFragment { FragmentBottomRedBinding binding; ProgressDialog progressDialog; // FirebaseDatabase firebaseDatabase; // DatabaseReference databaseReference; // DatabaseReference price; int quantity = 1; String coin; int icoin = 0; String mobile; int ivalue; int tcmoney; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = FragmentBottomRedBinding.inflate(inflater, container, false); View view = binding.getRoot(); String qty = String.valueOf(quantity); binding.quantityRed.setText(qty); mobile = getActivity().getIntent().getStringExtra("mobile"); progressDialog = new ProgressDialog(getActivity()); progressDialog.setTitle("Joining Red"); progressDialog.setMessage("Please Wait"); MaterialButtonToggleGroup toggleGroup = binding.toggleButton; int buttonId = toggleGroup.getCheckedButtonId(); MaterialButton button = toggleGroup.findViewById(buttonId); toggleGroup.addOnButtonCheckedListener(new MaterialButtonToggleGroup.OnButtonCheckedListener() { @Override public void onButtonChecked(MaterialButtonToggleGroup group, int checkedId, boolean isChecked) { if (group.getCheckedButtonId() == R.id.btn1) { icoin = 10; int ten = icoin * quantity; coin = String.valueOf(ten); binding.totalPriceMoney.setText(coin); } else if (group.getCheckedButtonId() == R.id.btn2) { icoin = 50; int fifty = icoin * quantity; coin = String.valueOf(fifty); binding.totalPriceMoney.setText(coin); } else if (group.getCheckedButtonId() == R.id.btn3) { icoin = 100; int hundred = icoin * quantity; coin = String.valueOf(hundred); binding.totalPriceMoney.setText(coin); } else { Toast.makeText(getActivity(), "Please Choose a valid amount", Toast.LENGTH_SHORT).show(); } } }); binding.subtractRed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { decrement(); } }); binding.addRed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { increment(); } }); binding.joinRed.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { tcmoney = Integer.parseInt(binding.totalPriceMoney.getText().toString()); if (tcmoney == 0 || icoin == 0) { Toast.makeText(getActivity(), "Please choose Correct Values", Toast.LENGTH_SHORT).show(); } else { progressDialog.show(); progressDialog.setCanceledOnTouchOutside(false); joinVerify(); } } }); return view; } private void joinVerify() { Toast.makeText(getContext(), mobile, Toast.LENGTH_SHORT).show(); DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Users").child(mobile).child("wallet"); databaseReference.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { String value = snapshot.getValue(String.class); ivalue = Integer.parseInt(value); if (tcmoney > ivalue) { progressDialog.dismiss(); Toast.makeText(getContext(), "Insufficient Funds", Toast.LENGTH_LONG).show(); } else { int ijoin = ivalue - tcmoney; String join = String.valueOf(ijoin); databaseReference.setValue(join); Toast.makeText(getActivity(), "Joined Successfully", Toast.LENGTH_SHORT).show(); progressDialog.dismiss(); } } @Override public void onCancelled(@NonNull DatabaseError error) { } }); } private void increment() { quantity++; String s = String.valueOf(quantity); binding.quantityRed.setText(s); int calc = icoin * quantity; coin = String.valueOf(calc); binding.totalPriceMoney.setText(coin); } private void decrement() { quantity = quantity > 1 ? --quantity : 1; String s = String.valueOf(quantity); binding.quantityRed.setText(s); int calc = icoin * quantity; coin = String.valueOf(calc); binding.totalPriceMoney.setText(coin); } }
fragment_bottom_red.xml
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".Fragments.BottomRedFragment"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:background="@color/red_a700" android:fontFamily="@font/poppins" android:gravity="center" android:text="red" android:textAllCaps="true" android:textColor="@color/white" android:textSize="30sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:background="@color/white" android:gravity="center" android:text="CONTRACT MONEY" android:textColor="@color/red_a700" android:textSize="30dp" android:textStyle="bold" /> <com.google.android.material.button.MaterialButtonToggleGroup android:id="@+id/toggleButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="30dp" app:singleSelection="true"> <Button android:id="@+id/btn1" style="?attr/materialButtonOutlinedStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:focusable="true" android:text="₹10" android:textColor="@color/red_a700" /> <Button android:id="@+id/btn2" style="?attr/materialButtonOutlinedStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="₹50" android:textColor="@color/red_a700" /> <Button android:id="@+id/btn3" style="?attr/materialButtonOutlinedStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="₹100" android:textColor="@color/red_a700" /> </com.google.android.material.button.MaterialButtonToggleGroup> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:gravity="center" android:orientation="horizontal"> <Button android:id="@+id/subtractRed" android:layout_width="wrap_content" android:layout_height="75dp" android:layout_marginEnd="30dp" android:backgroundTint="@color/red_a700" android:text="-" android:textColor="@color/white" android:textSize="30dp" /> <TextView android:id="@+id/quantityRed" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:text="1" android:textColor="@color/black" android:textSize="60sp" /> <Button android:id="@+id/addRed" android:layout_width="wrap_content" android:layout_height="75dp" android:layout_marginStart="30dp" android:backgroundTint="@color/red_a700" android:text="+" android:textColor="@color/white" android:textSize="30dp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:gravity="center_horizontal" android:orientation="horizontal"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:text="Total order money is ₹" android:textColor="@color/black" android:textSize="20sp" /> <TextView android:id="@+id/totalPriceMoney" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:text="0" android:textAlignment="center" android:textColor="@color/black" android:textSize="20sp" /> </LinearLayout> <Button android:id="@+id/joinRed" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginTop="20dp" android:backgroundTint="#B71C1C" android:fontFamily="@font/aladin" android:paddingStart="50dp" android:paddingTop="10dp" android:paddingEnd="50dp" android:paddingBottom="10dp" android:text="Join" android:textColor="@color/white" android:textSize="20sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginTop="20dp" android:textColor="@color/black" android:layout_marginBottom="30dp" android:fontFamily="@font/poppins" android:text="By clicking JOIN Button you \n agree to our presale rule" android:textSize="16sp" android:textStyle="bold" /> </LinearLayout> </FrameLayout>
问题根因
核心问题出在joinVerify方法中使用了addValueEventListener监听钱包节点:
addValueEventListener是持久化监听器,只要监听的节点数据发生变更,就会自动触发onDataChange回调- 第一次扣款成功后,wallet节点数值更新,监听器被再次触发,重复执行扣款逻辑,形成循环直到余额小于扣款金额为止
修复方案
将joinVerify方法中的addValueEventListener替换为addListenerForSingleValueEvent即可,该监听器只会读取一次节点数据,读取完成后自动销毁,不会重复触发回调。
修改后的joinVerify方法示例:
private void joinVerify() { Toast.makeText(getContext(), mobile, Toast.LENGTH_SHORT).show(); DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Users").child(mobile).child("wallet"); // 替换为单次读取的监听器 databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { String value = snapshot.getValue(String.class); ivalue = Integer.parseInt(value); if (tcmoney > ivalue) { progressDialog.dismiss(); Toast.makeText(getContext(), "Insufficient Funds", Toast.LENGTH_LONG).show(); } else { int ijoin = ivalue - tcmoney; String join = String.valueOf(ijoin); databaseReference.setValue(join); Toast.makeText(getActivity(), "Joined Successfully", Toast.LENGTH_SHORT).show(); progressDialog.dismiss(); // 可选:操作完成后关闭弹窗 dismiss(); } } @Override public void onCancelled(@NonNull DatabaseError error) { progressDialog.dismiss(); } }); }
优化建议
- 涉及余额修改的操作建议使用Firebase Transaction事务处理,避免多端同时操作时出现数据并发不一致的问题
- 钱包余额建议直接存储为数字类型,避免字符串转数字时可能出现的格式异常
- 点击加入按钮后可以先置灰按钮,避免用户重复点击提交多次请求
内容的提问来源于stack exchange,提问作者gauravmandall
相关产品推荐
相关产品推荐

