是否可以从 Firebase 同步加载数据?

Is it possible to synchronously load data from Firebase?

我正在尝试使用从通过 Firebase 连接的对等点获取的数据更新我的 Android 应用程序中 WebView 的部分内容。为此,执行将 return 所需数据的阻塞操作可能会有所帮助。例如,聊天示例的一个实现将等待另一个聊天参与者在 push.setValue() 到 return 之前写一些东西。 Firebase 可以实现这种行为吗?

在常规 JVM 上,您将使用常规 Java 同步原语执行此操作。

例如:

// create a java.util.concurrent.Semaphore with 0 initial permits
final Semaphore semaphore = new Semaphore(0);

// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
    // onDataChange will execute when the current value loaded and whenever it changes
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // TODO: do whatever you need to do with the dataSnapshot

        // tell the caller that we're done
        semaphore.release();
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {

    }
});

// wait until the onDataChange callback has released the semaphore
semaphore.acquire();

// send our response message
ref.push().setValue("Oh really? Here is what I think of that");

但这不适用于 Android。这是一件好事,因为在影响用户界面的任何事情中使用这种类型的阻塞方法都是一个坏主意。我有这段代码的唯一原因是因为我需要进行单元测试。

在真正的面向用户的代码中,您应该采用事件驱动的方法。因此,与其“等待数据到来,然后发送我的消息”,不如“当数据到来时,发送我的消息”:

// attach a value listener to a Firebase reference
ref.addValueEventListener(new ValueEventListener() {
    // onDataChange will execute when the current value loaded and whenever it changes
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // TODO: do whatever you need to do with the dataSnapshot

        // send our response message
        ref.push().setValue("Oh really? Here is what I think of that!");
    }

    @Override
    public void onCancelled(FirebaseError firebaseError) {
        throw firebaseError.toException();
    }
});

最终结果完全相同,但此代码不需要同步并且不会在 Android 上阻塞。

import com.google.android.gms.tasks.Tasks;

Tasks.await(taskFromFirebase);

我想出了另一种同步获取数据的方法。 先决条件是不在 UI 线程上。

final TaskCompletionSource<List<Objects>> tcs = new TaskCompletionSource<>();

firebaseDatabase.getReference().child("objects").addListenerForSingleValueEvent(new ValueEventListener() {

            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                Mapper<DataSnapshot, List<Object>> mapper = new SnapshotToObjects();
                tcs.setResult(mapper.map(dataSnapshot));
            }

            @Override
            public void onCancelled(DatabaseError databaseError) { 
                tcs.setException(databaseError.toException());
            }

        });

Task<List<Object>> t = tcs.getTask();

try {
    Tasks.await(t);
} catch (ExecutionException | InterruptedException e) {
    t = Tasks.forException(e);
}

if(t.isSuccessful()) {
    List<Object> result = t.getResult();
}

我测试了我的解决方案,它工作正常,但请证明我错了!

这是一个基于 Alex 简洁回答的更长示例:

import com.google.android.gms.tasks.Tasks;
import com.google.firebase.firestore.CollectionReference;
import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QuerySnapshot;<br>
final FirebaseFirestore firestore = FirebaseFirestore.getInstance();
final CollectionReference chatMessageReference = firestore.collection("chatmessages");
final Query johnMessagesQuery = chatMessageReference.whereEqualTo("name", "john");<br>
<b>final QuerySnapshot querySnapshot = Tasks.await(johnMessagesQuery.get());</b><br>
final List<DocumentSnapshot> johnMessagesDocs = querySnapshot.getDocuments();
final ChatMessage firstChatMessage = johnMessagesDocs.get(0).toObject(ChatMessage.class);</pre>

请注意,这 不是 好的做法,因为它会阻塞 UI 线程,通常应该改用回调。但在这种特殊情况下,这会有所帮助。

如果有人也在考虑如何使用 Kotlin 的协程,你可以使用 kotlinx-coroutines-play-services

添加到您的应用 build.gradle 文件:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1"

然后简单地:

suspend fun signIn(email: String, password: String) {
    try {
        val auth: FirebaseAuth = FirebaseAuth.getInstance()
        auth.signInWithEmailAndPassword(email, password).await()
    } catch (e: FirebaseAuthException) {
        println("${e.errorCode}: ${e.message}")
    }
}

我做了一个简单的class来在Android中同步调用任务。
请注意,这类似于 Javascript 的异步等待功能。
检查我的 gist

这是使用它的示例代码。

TasksManager.call(() -> {
    Tasks.await(AuthManager.signInAnonymously());

    // You can use multiple Tasks.await method here.
    // Tasks.await(getUserTask());
    // Tasks.await(getProfileTask());
    // Tasks.await(moreAwesomeTask());
    // ...

    startMainActivity();
    return null;
}).addOnFailureListener(e -> {
    Log.w(TAG, "signInAnonymously:ERROR", e);
});