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
相关产品推荐
相关产品推荐

