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

Firestore异步回调致注册逻辑顺序错误的解决方案咨询

问题描述

我做了一个收集邮箱和密码的注册表单,原本逻辑是:用户输入数据后,先检查Firestore数据库中是否存在对应邮箱的文档,不存在就插入注册数据。但当前代码总是先执行插入操作,再执行邮箱存在性检查,导致已有邮箱的账号被重复插入。比如数据库里已有"test@gmail.com"的文档,提交相同邮箱时,本该提示“账号已存在,无法注册”,但代码会先插入数据再做检查。希望找到不用把插入代码放到检查回调里的解决办法。

XML布局代码

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout 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"
    tools:context=".MainActivity"
    android:orientation="vertical"
    android:paddingTop="30dp">

    <View
        android:layout_width="400dp"
        android:layout_height="1dp"
        android:background="#678049"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="30dp"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Email"
            android:textSize="20dp"
            android:textColor="@color/black"
            android:layout_marginLeft="5dp"
            android:layout_gravity="center|top"/>

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <EditText
            android:id="@+id/UserEmail"
            android:layout_width="345dp"
            android:layout_height="40dp"
            android:textSize="20dp"
            android:background="#5676"
            android:hint="Enter  Email Adress.."
            android:inputType="text"
            android:layout_marginLeft="3dp"/>

    </LinearLayout>
    <Space
        android:layout_width="match_parent"
        android:layout_height="5dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Password"
            android:textColor="@color/black"
            android:textSize="20dp"
            android:layout_marginLeft="5dp" />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_marginTop="5dp">

        <EditText
            android:id="@+id/UserPassword"
            android:layout_width="345dp"
            android:layout_height="40dp"
            android:textSize="20dp"
            android:background="#5676"
            android:hint="Enter Password.."
            android:layout_marginVertical="13dp"
            android:inputType="textPassword" />

    </LinearLayout>

    <Button
        android:id="@+id/btnSubmit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dp"
        android:padding="10dp"
        android:layout_marginHorizontal="80dp"
        android:backgroundTint="#4C4B4B"
        android:text="Sign up"
        android:textColor="#F5FBF6" />

</LinearLayout>

Java逻辑代码

public class MainActivity extends AppCompatActivity {
    public static final String COLLECTION_USER = "collection_user";
    EditText userEmail, userPassword;
    Button btnSignUp;
    boolean accountAlreadyAvailable = false;
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        userEmail = findViewById(R.id.UserEmail);
        userPassword = findViewById(R.id.UserPassword);
        btnSignUp = findViewById(R.id.btnSubmit);

        btnSignUp.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String email = userEmail.getText().toString();
                String password = userPassword.getText().toString();

                Map<String, Object> data = new HashMap<>();
                data.put("email", email);
                data.put("password", password);

                FirebaseFirestore db = FirebaseFirestore.getInstance();
                db.collection(COLLECTION_USER).document(email)
                        .get()
                        .addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
                            @Override
                            public void onComplete(@NonNull Task<DocumentSnapshot> task) {
                                if(task.getResult().exists()) {
                                    accountAlreadyAvailable = true;
                                    Log.d(TAG, "Account available changed to: " + accountAlreadyAvailable);
                                }
                            }
                        });

                Log.d(TAG, "Account available: " + accountAlreadyAvailable);
                if(accountAlreadyAvailable == false) {
                    db.collection(COLLECTION_USER).document(email).set(data).addOnCompleteListener(new OnCompleteListener<Void>() {
                        @Override
                        public void onComplete(@NonNull Task<Void> task) {
                            if(task.isSuccessful()) {
                                Log.d(TAG, "Data inserted as no account found");
                            }
                        }
                    });
                } else {
                    Log.d(TAG, "Account available. Can not sign up!");
                }
            }
        });
    }
}
解决方案

原因分析

问题核心是Firestore的get()方法为异步执行,代码触发检查后,不等检查结果返回就直接执行插入判断逻辑,此时accountAlreadyAvailable仍为初始值false,所以总会执行插入操作。

方案1:使用Firestore事务(原子性操作,无需嵌套回调)

Firestore事务能保证检查和插入操作的原子性,避免并发冲突,同时无需将插入代码嵌套在检查回调内:

btnSignUp.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        final String email = userEmail.getText().toString();
        final String password = userPassword.getText().toString();

        FirebaseFirestore db = FirebaseFirestore.getInstance();
        db.runTransaction(new Transaction.Function<Void>() {
            @Override
            public Void apply(@NonNull Transaction transaction) throws FirebaseFirestoreException {
                DocumentReference docRef = db.collection(COLLECTION_USER).document(email);
                DocumentSnapshot snapshot = transaction.get(docRef);
                
                if (snapshot.exists()) {
                    // 账号已存在,抛出异常终止事务
                    throw new FirebaseFirestoreException("账号已存在,无法注册", FirebaseFirestoreException.Code.ALREADY_EXISTS);
                }
                
                // 账号不存在,插入数据
                Map<String, Object> data = new HashMap<>();
                data.put("email", email);
                data.put("password", password);
                transaction.set(docRef, data);
                return null;
            }
        }).addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                Log.d(TAG, "注册成功");
                // 可添加注册成功的UI提示或页面跳转逻辑
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                if (e instanceof FirebaseFirestoreException && ((FirebaseFirestoreException) e).getCode() == FirebaseFirestoreException.Code.ALREADY_EXISTS) {
                    Log.d(TAG, "账号已存在,无法注册");
                    // 可添加账号已存在的UI提示
                } else {
                    Log.d(TAG, "注册失败: " + e.getMessage());
                }
            }
        });
    }
});

方案2:使用ListenableFuture(Java同步化异步操作)

引入Guava库的ListenableFuture,将异步操作转为同步逻辑,避免回调嵌套:

btnSignUp.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        String email = userEmail.getText().toString();
        String password = userPassword.getText().toString();

        FirebaseFirestore db = FirebaseFirestore.getInstance();
        ListenableFuture<DocumentSnapshot> future = db.collection(COLLECTION_USER).document(email).get();
        
        Futures.addCallback(future, new FutureCallback<DocumentSnapshot>() {
            @Override
            public void onSuccess(@Nullable DocumentSnapshot result) {
                if (result != null && result.exists()) {
                    Log.d(TAG, "账号已存在,无法注册");
                } else {
                    Map<String, Object> data = new HashMap<>();
                    data.put("email", email);
                    data.put("password", password);
                    db.collection(COLLECTION_USER).document(email).set(data)
                            .addOnSuccessListener(aVoid -> Log.d(TAG, "注册成功"))
                            .addOnFailureListener(e -> Log.d(TAG, "注册失败: " + e.getMessage()));
                }
            }

            @Override
            public void onFailure(Throwable t) {
                Log.d(TAG, "检查账号失败: " + t.getMessage());
            }
        }, Executors.newSingleThreadExecutor());
    }
});

额外建议

  • 不要明文存储密码,建议使用Firebase Auth进行账号认证,它会自动处理邮箱唯一性校验,且密码会被加密存储。
  • 对用户输入的邮箱、密码做基础格式校验(如邮箱格式、密码长度),提升用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:35:11