如何让自定义函数在Firebase的onComplete方法执行完毕后再返回结果?
问题本质
这是Firebase Firestore SDK的典型异步调用问题:docRef.get()是非阻塞的异步方法,调用后会立刻返回,请求逻辑在后台线程执行,onComplete回调要等请求完成后才会在主线程触发。所以你当前写法里的return语句永远会在回调修改value[0]之前执行,拿到的只能是初始赋值的true。
方案1:自定义回调接口(Java项目通用方案)
先定义一个结果回调接口:
// 定义回调接口,用来返回异步校验结果 public interface CheckMaxCallback { void onResult(boolean isExceedMaxLimit); }
改造原有的checkMax方法,不要同步返回boolean值,改为通过回调返回结果:
private void checkMax(String email, CheckMaxCallback callback) { DocumentReference docRef = db.collection("user_bookings").document(email).collection(day).document("0"); docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() { @Override public void onComplete(@NonNull Task<DocumentSnapshot> task) { boolean result; if (task.isSuccessful()) { DocumentSnapshot doc = task.getResult(); if (doc.exists()) { Long slot1 = doc.getLong("slot1"); Long slot2 = doc.getLong("slot2"); if(slot1 == -1 && slot2 == -1){ result = false; } else if(slot1 == -1 || slot2 == -1){ if(durationPosition == 1){ Toast.makeText(BookingActivity.this, "超出最大可预订时长,请缩短预订时间", Toast.LENGTH_SHORT).show(); result = true; } else { result = false; } } else { Toast.makeText(BookingActivity.this, "你所选日期的预订次数已达上限", Toast.LENGTH_SHORT).show(); result = true; } } else { result = false; Log.e(TAG, "校验结果值 = " + result); Log.d(TAG, "CHECKMAX 未找到对应文档"); } } else { result = true; Log.d(TAG, "请求失败 ", task.getException()); } // 将结果通过回调抛出 callback.onResult(result); } }); }
调用方式改为传入回调处理后续逻辑:
// 原来调用 boolean res = checkMax(email); 的位置替换为下面的代码 checkMax(email, new CheckMaxCallback() { @Override public void onResult(boolean isExceedMaxLimit) { // 所有依赖校验结果的逻辑都要写在这里 if (isExceedMaxLimit) { // 超出限制的处理逻辑 } else { // 校验通过的处理逻辑 } } });
方案2:Kotlin协程方案(Kotlin项目适用)
如果你的项目使用Kotlin开发,可以配合Firebase官方KTX扩展用挂起函数同步写法实现异步逻辑,无需嵌套回调:
private suspend fun checkMax(email: String): Boolean { val docRef = db.collection("user_bookings").document(email).collection(day).document("0") return try { val doc = docRef.get().await() if (doc.exists()) { val slot1 = doc.getLong("slot1") val slot2 = doc.getLong("slot2") when { slot1 == -1L && slot2 == -1L -> false slot1 == -1L || slot2 == -1L -> { if (durationPosition == 1) { Toast.makeText(this@BookingActivity, "超出最大可预订时长,请缩短预订时间", Toast.LENGTH_SHORT).show() true } else false } else -> { Toast.makeText(this@BookingActivity, "你所选日期的预订次数已达上限", Toast.LENGTH_SHORT).show() true } } } else { false } } catch (e: Exception) { Log.d(TAG, "请求失败 ", e) true } }
调用时在页面生命周期协程域中执行即可:
lifecycleScope.launch { val isExceedLimit = checkMax(email) // 处理校验结果 }
不推荐方案:CountDownLatch阻塞等待
不要在生产环境使用该方案,会阻塞主线程,请求超时或网络差时会直接触发ANR(应用无响应):
private boolean checkMax(String email){ final boolean[] value = new boolean[1]; CountDownLatch latch = new CountDownLatch(1); value[0] = true; DocumentReference docRef = db.collection("user_bookings").document(email).collection(day).document("0"); docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() { @Override public void onComplete(@NonNull Task<DocumentSnapshot> task) { // 原有校验逻辑不变,处理完value[0]后执行计数减1 if (task.isSuccessful()) { DocumentSnapshot doc = task.getResult(); if (doc.exists()) { Long slot1 = doc.getLong("slot1"); Long slot2 = doc.getLong("slot2"); if(slot1 == -1 && slot2 == -1){ value[0] = false; } else if(slot1 == -1 || slot2 == -1){ if(durationPosition == 1){ Toast.makeText(BookingActivity.this, "超出最大可预订时长,请缩短预订时间", Toast.LENGTH_SHORT).show(); value[0] = true; } else value[0] = false; } else{ Toast.makeText(BookingActivity.this, "你所选日期的预订次数已达上限", Toast.LENGTH_SHORT).show(); value[0] = true; } } else { value[0] = false; Log.e(TAG, "校验结果值 = " + value[0]); Log.d(TAG, "CHECKMAX 未找到对应文档"); } } else { value[0] = true; Log.d(TAG, "请求失败 ", task.getException()); } latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } return value[0]; }
内容的提问来源于stack exchange,提问作者Zowie Tay
相关产品推荐
相关产品推荐

