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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 15:27:00