Firebase回调在测试过程中运行,但不在运行时运行 [英] Firebase callbacks work during testing but not during runtime
问题描述
用户获得一个密钥,然后可以将自己连接到相应的DatabaseReference。
我们需要检查密钥是否有效。因此,在创建组时,主机会自动将自己添加到用户列表中。然后所有新客户端可以检查列表中是否有条目。如果列表为空,键是无效的。
这意味着我需要等待一个setValue调用的完成。
Firebase有很多回调可以告诉我这个问题,但是它们相当麻烦。有时候,他们根本就不叫。
我已经在这里问了一个关于这个非确定性行为的问题:如何侦听Firebase setValue完成
现在我发现了这些回调的一个新问题。
我已将基础设施更改为异步设置。所有的交互都打包成Callables并提交给一个ExecutorService。结果是未来<>。现在,如果我想等待一些事情的完成,我可以等待这个未来。在Future的内部,我仍然需要使用Firebase回调函数。
$ b
代码位于一个名为DBConnection的包装类中。
这里是我创建一个新的群组(派对)的代码:
public Future< DBState> createParty(){
//断言条目
assertState(DBState.SignedIn);
//进程状态转换
可调用< DBState> creationProcess = new Callable< DBState>(){
@Override
public DBState call()throws Exception {
lock.lock();
尝试{
//创建新的派对
ourPartyDatabaseReference = partiesDatabaseReference.push();
usersDatabaseReference = ourPartyDatabaseReference.child(users);
//为缺少的回调尝试每种补救措施
firebaseDatabase.goOnline();
ourPartyDatabaseReference.keepSynced(true);
usersDatabaseReference.keepSynced(true);
//将值推送到用户数据库
//这样数据库引用实际上被创建
//并且新用户可以在连接$ b时搜索现有用户
$ b //我们只能在任务完成之后继续
//为成功和失败添加监听器并等待完成
// TODO:我们需要信息这个任务已经完成了
//但是没有回调似乎工作
// onSuccess,onCompletion对任务是不可靠的
//并且userDatabaseReference上的child和value事件监听器是不可靠,太
最后CountDownLatch服务员=新CountDownLatch(1);
usersDatabaseReference.addValueEventListener(new ValueEventListener(){
@Override
public void onDataChange(DataSnapshot dataSnapshot){
waiter.countDown();
}
@Override
public void onCancelled(DatabaseError databaseError){
waiter.countDown();
}
});
任务addingTask = usersDatabaseReference.child(user.getUid())。setValue(true);
addingTask.addOnSuccessListener(new OnSuccessListener(){
@Override
public void onSuccess(Object o){
waiter.countDown();
}
});
addingTask.addOnCompleteListener(new OnCompleteListener(){
@Override $ b $ public void onComplete(@NonNull Task task){
waiter.countDown();
}
});
试试{
waiter.await();
} catch(InterruptedException ex){
}
connectToParty();
} finally {
lock.unlock();
}
//如果可以连接,我们现在是DBState.Connected,
//否则我们仍然是DBState.SignedIn
返回状态;
}
};
//启动过程
return executorService.submit(creationProcess);
你可以像这样使用它: 未来< DBState> creationFuture = dbConnection.createParty();
尝试{
creationFuture.get(TIMEOUT,TimeUnit.MILLISECONDS);
} catch(InterruptedException ex){
抛出新的AssertionError(应该没有中断);
} catch(TimeoutException ex){
throw new AssertionError(创建派对时超时);
} catch(ExecutionException ex){
throw new AssertionError(concurrent execution exception);
}
我已经为此写了测试。
在测试中,一切正常。我已经执行了至少十几次的 canCreateParty
测试。
为了确保回调工作正常,我已经将 CountDownLatch
增加到3个计数,并将断点添加到 countDowns
中。每一个 countDown
都已经达到。
但是在运行时,不会调用任何回调函数。
没有达到任何断点,等待未来最终超时。
最奇怪的部分是:我有firebase控制台打开旁边的模拟器。我可以看到如何创建新的方和添加用户。无论是在测试中还是在运行时,派对创作都如预期的那样工作,并添加了新用户。
为什么我在运行时没有回调?
解决方案原因是Firebase总是调用main线程。
我的测试中的main线程被称为junittestrunnerXXX。 Firebase创建了一个名为main的新线程来调用回调。
在运行时,main线程是实际的main线程。如果我打电话给get(),它会被阻塞。 Firebase检查这个线程是否存在,并且由于它已经存在,并且被阻塞,所以没有任何反应。
I'm using Firebase in an Android app. The app sets up something like a groupchat and allows users to join.
The users get a key and can then connect themselves to the corresponding DatabaseReference.
We need a check whether the key is valid. Therefore, when the group is created, the host automatically adds himself to a list of users. Then all new clients can check if there are entries in the list. If the list is empty, the key is invalid.
This means that I need to wait for the completion of a setValue call.
Firebase has many callbacks that can tell me about this, but they are quite problematic. Sometimes, they simply aren't called.
I've already asked a question about this non-deterministic behaviour here: How to listen for Firebase setValue completion
Now I've found a new problem with those callbacks.
I've changed my infrastructure to an asynchronous setup. All interactions are packaged into Callables and submitted to an ExecutorService. The result is a Future<>. Now, if I want to wait for something to complete, I can just wait on that future. Inside of the Future, I still need to use the Firebase callbacks.
The code is in a wrapper class called DBConnection.
Here is my code for creating a new group (party) :
public Future<DBState> createParty() {
// assert entries
assertState(DBState.SignedIn);
// process for state transition
Callable<DBState> creationProcess = new Callable<DBState>() {
@Override
public DBState call() throws Exception {
lock.lock();
try {
// create a new party
ourPartyDatabaseReference = partiesDatabaseReference.push();
usersDatabaseReference = ourPartyDatabaseReference.child("users");
// try every remedy for the missing callbacks
firebaseDatabase.goOnline();
ourPartyDatabaseReference.keepSynced(true);
usersDatabaseReference.keepSynced(true);
// push a value to the users database
// this way the database reference is actually created
// and new users can search for existing users when they connect
// we can only continue after that task has been completed
// add listeners for success and failure and wait for their completion
// TODO: we need information that this task has been finished
// but no callback seems to work
// onSuccess, onCompletion on the task are not reliable
// and the child and value event listeners on the userDatabaseReference are not reliable, too
final CountDownLatch waiter = new CountDownLatch(1);
usersDatabaseReference.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
waiter.countDown();
}
@Override
public void onCancelled(DatabaseError databaseError) {
waiter.countDown();
}
});
Task addingTask = usersDatabaseReference.child(user.getUid()).setValue(true);
addingTask.addOnSuccessListener(new OnSuccessListener() {
@Override
public void onSuccess(Object o) {
waiter.countDown();
}
});
addingTask.addOnCompleteListener(new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
waiter.countDown();
}
});
try {
waiter.await();
} catch (InterruptedException ex) {
}
connectToParty();
} finally {
lock.unlock();
}
// if we could connect, we are now DBState.Connected,
// otherwise we are still DBState.SignedIn
return state;
}
};
// start process
return executorService.submit(creationProcess);
}
You can use it like this:
Future<DBState> creationFuture = dbConnection.createParty();
try {
creationFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
throw new AssertionError("there should be no interrupt");
}catch (TimeoutException ex) {
throw new AssertionError("timeout in party creation");
}catch (ExecutionException ex) {
throw new AssertionError("concurrent execution exception");
}
I've written tests for this.
And in the tests, everything works fine. I've executed the canCreateParty
test at least a dozen times now.
To make sure, that the callbacks work, I've increased the CountDownLatch
to 3 counts and added breakpoints to the countDowns
. Every countDown
is reached.
But at runtime, no callback is ever called.
None of the breakpoints are reached, the waiting for the future eventually times out.
The strangest part is: I have the firebase console open right next to the emulator. I can see how new parties are created and users are added. Both for the tests and at runtime, the party creation works just as expected and a new user is added.
Why am I getting no callback at runtime ?
解决方案 The reason is that Firebase always calls its callbacks from the main thread.
The "main" thread in my tests is called something like "junittestrunnerXXX". And Firebase creates a new thread called "main" to call the callbacks.
At runtime, the "main" thread is the actual "main" thread. If I call get() on that, it is blocked for good. Firebase checks if this thread exists and since it already exists and since it is blocked, nothing happens.
这篇关于Firebase回调在测试过程中运行,但不在运行时运行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!