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

Android 是否可以从Firebase同步加载数据?,android,firebase,firebase-realtime-database,Android,Firebase,Firebase Realtime Database,我正试图用我从Firebase连接的对等网络获取的数据更新Android应用程序中的部分网络视图。为此,执行将返回所需数据的阻塞操作可能会有所帮助。例如,聊天示例的一个实现,它将等待另一个聊天参与者在push.setValue()返回之前写入内容。 Firebase有可能出现这种行为吗?在常规JVM上,您可以使用常规Java同步原语来实现这一点 例如: // create a java.util.concurrent.Semaphore with 0 initial permits final

我正试图用我从Firebase连接的对等网络获取的数据更新Android应用程序中的部分网络视图。为此,执行将返回所需数据的阻塞操作可能会有所帮助。例如,聊天示例的一个实现,它将等待另一个聊天参与者在push.setValue()返回之前写入内容。
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上不起作用。这是一件好事,因为在任何影响用户界面的事情中使用这种类型的阻塞方法都不是一个好主意。我之所以有这段代码,唯一的原因是因为我需要进行单元测试

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


最终结果完全相同,但这段代码不需要同步,也不会在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();
}
final TaskCompletionSource tcs=new TaskCompletionSource();
firebaseDatabase.getReference().child(“对象”).addListenerForSingleValueEvent(新值EventListener()){
@凌驾
公共void onDataChange(DataSnapshot DataSnapshot){
映射器映射器=新快照对象();
setResult(mapper.map(dataSnapshot));
}
@凌驾
已取消的公共void(DatabaseError DatabaseError){
setException(databaseError.toException());
}
});
Task t=tcs.getTask();
试一试{
任务。等待(t);
}捕获(ExecutionException | InterruptedException e){
t=任务。预例外(e);
}
如果(t.isSuccessful()){
List result=t.getResult();
}

我测试了我的解决方案,效果很好,但请证明我错了

以下是一个基于Alex简洁答案的较长示例:

import com.google.android.gms.tasks.tasks;
导入com.google.firebase.firestore.CollectionReference;
导入com.google.firebase.firestore.DocumentSnapshot;
导入com.google.firebase.firestore.FirebaseFirestore;
导入com.google.firebase.firestore.Query;
导入com.google.firebase.firestore.QuerySnapshot
final FirebaseFirestore firestore=FirebaseFirestore.getInstance(); 最终收集参考chatMessageReference=firestore.collection(“chatmessages”); final Query johnmessagequery=chatMessageReference.whereEqualTo(“name”,“john”)
final QuerySnapshot QuerySnapshot=Tasks.await(johnmessagequery.get())
最终列表johnMessageDocs=querySnapshot.getDocuments(); final ChatMessage firstChatMessage=johnMessagesDocs.get(0.toObject)(ChatMessage.class)


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

如果有人也在考虑如何使用Kotlin的协同程序,您可以使用它

添加到你的应用程序
build.gradle
文件:

implementation“org.jetbrains.kotlinx:kotlinx协同程序播放服务: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}")
    }
}

我制作了一个简单的类来在Android中同步调用任务。
请注意,这类似于Javascript的异步等待函数。
检查我的电脑

下面是使用它的示例代码。

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);
});

谢谢你,弗兰克!我同意阻塞vs事件驱动的方法。但是,我必须对WebView执行多个更新,就像WebView.loadDataWithBaseURL()一样,HTTP请求(WebView执行的)也会被阻止。我必须这样做而不是WebView本身的原因是服务器无法接近应用程序,我使用Firebase作为“隧道”。如果有更好的解决方案,我们将不胜感激。另外,如果阻塞部分位于一个事件中,并且每次WebView请求更新时都会触发该事件,那么信号量解决方案不是会失败吗?我很确定上面的代码片段解决了您给出的聊天示例。如果您的用例实际上不同,请提供一个。代码在一周中的每一天都胜过对代码的描述。@Dani它是如何工作的?因为acquire将阻止主线程,而发布将永远不会执行,因为它也应该在主线程上运行?@Frank:您使用信号量的示例可能在早期版本的Firebase上有效,但在9.0.2上不起作用。我试过了。侦听器回调在主线程上运行,主线程在等待获取信号量时被阻塞。当我使用semaphore.tryAcquire(10,TimeUnit.SECONDS)
时,它总是超时,然后启动回调。问题存在,ios提供了良好的集中线程机制。使用parseapi,您可以通过调用同步api来使用它们。Firebase真是糟透了谢谢,我也在找这个;但这如何解决“执行将返回所需数据的阻塞操作”?事件监听器不是任务,所以如何做到这一点呢?这个监听器以阻塞方式从firebase执行任务,您要么得到响应,要么抛出错误。因此,您不需要任何回调听起来不错,再详细一点,在我投票支持答案之前,链接和背景会很好:)Firebase团队现在已经否决了任务,并建议我们使用ApiFutures:这个新的SnapshotToObjects()是什么;它是任何内置类还是您已经创建了自己的类。如果它是你自己的类,你能告诉我它和它的参数吗?它是由我实现的,只是DataSnapshot->List的映射器。现在这个例子有了真正的实现。在我的例子中,它将DataSnapshot转换为配方列表。你可以找到一个例子
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}")
    }
}
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);
});