如何等待 Firebase 任务完成以获取结果作为等待函数

我正在为 Android 使用 Java 上的 Firebase 的实时数据库,我正在尝试编写一组帮助函数来促进从/向 RTDB 读取和写入函数。

我的函数如下所示,应该返回数据库中我的停车对象的 HashMap;我获得了对我的数据库的引用并添加了一个 onSuccessListener ,我在其中迭代快照并将每个 Parking 对象添加到我的 HashMap 并返回 HashMap parkings

问题是在 onSuccessListener 运行之前函数返回 parkings 没有值。

public static ArrayList<Parking> getParkingLots() {
    DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
    Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
    ArrayList<Parking> parkings = new ArrayList<Parking>();
    dataSnapshotTask.addOnSuccessListener(new OnSuccessListener<DataSnapshot>() {
        @Override
        public void onSuccess(DataSnapshot dataSnapshot) {
            Iterable<DataSnapshot> parkingsData = dataSnapshot.getChildren();
            for (DataSnapshot parking :
                    parkingsData) {
                parkings.add(parking.getValue(Parking.class));
            }
        }
    });
    return parkings;
}

我也尝试了这个实现,我直接尝试从任务 datSnapshotTask 中获取结果,但我得到一个异常抛出 java.lang.IllegalStateException: Task is not yet complete

   public static HashMap<String, Parking> getParkingLots() {
    DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
    Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
    Iterable<DataSnapshot> parkingsData = dataSnapshotTask.getResult().getChildren();
    HashMap<String, Parking> parkings = new HashMap<String, Parking>();
    for (DataSnapshot parking :
            parkingsData) {
        parkings.put(parking.getKey(), parking.getValue(Parking.class));
    }

    return parkings;
}

有没有办法以等待方式从任务中获取结果?

stack overflow How to wait for Firebase Task to complete to get result as an await function
原文答案
author avatar

接受的答案

数据是从 Firebase 异步加载的,因为它可能必须来自服务器。在加载数据时,您的主代码会继续运行。然后当数据可用时,任务完成并且您的 onSuccess 被调用。

通过在调试器中运行或添加一些日志记录,最容易了解这在实践中意味着什么:

DatabaseReference mDatabase = FirebaseDatabase.getInstance().getReference();
Log.i("Firebase", "1. Starting to load data");
Task<DataSnapshot> dataSnapshotTask = mDatabase.get();
for (DataSnapshot parking: parkingsData) {
    Log.i("Firebase", "2. Got data");
}
Log.i("Firebase", "3. After starting to load data");

当您运行此代码时,它会打印:

1.开始加载数据

2.开始加载数据后

  1. 获取数据

这可能不是您期望的顺序,但它实际上按预期工作。它还解释了为什么您没有从 getParkingLots 函数中得到结果:当您的 return parkings 运行时, parkings.add(parking.getValue(Parking.class)) 还没有被调用!


解决方案总是相同的:任何需要数据库数据的代码,都需要在您的 onSuccess 方法中或从那里调用。这意味着您不能从函数中返回停车场,但您可以传入一个将它们作为参数的回调。或者你可以返回一个与 Firebase 得到的 Task<HashMap<String, Parking>> 非常相似的 get()

有关回调的示例,请参阅我对以下内容的详细说明: getContactsFromFirebase() method return an empty list


答案:

作者头像

我认为在 java 中不可能以“等待方式”做。

但是在 kotlin 中,我们可以使用 coroutines

suspend fun getParkingLots(): HashMap<String, Parking> {
    val database: DatabaseReference = FirebaseDatabase.getInstance().reference
    val task: Task<DataSnapshot> = database.get()
    val deferredDataSnapshot: Deferred<DataSnapshot> = task.asDeferred()
    val parkingsData: Iterable<DataSnapshot> = deferredDataSnapshot.await().children // or can use just task.await()

    // ... use parkingsData
}

使用 Task.asDeferred 扩展功能,我们可以将一个 Task 转换为 Deferred 对象。或可以使用 Task.await() ,而无需将 task 转换为 Deferred 对象。

使用 Task.asDeferred() 扩展函数使用依赖项:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'

要调用 suspend 函数,我们需要启动一个Coroutine

someCoroutineScope.launch {
    val parkings = getParkingLots()
}

someCoroutineScopeCoroutineScope 实例。在Android中,它可以是 viewModelScope class和 ViewModel in lifecycleScopeActivityFragmentCoroutineScope ,也可以是 ```
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'