Firebase回调在测试过程中运行,但不在运行时运行 [英] Firebase callbacks work during testing but not during runtime

查看:190
本文介绍了Firebase回调在测试过程中运行,但不在运行时运行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在Android应用中使用Firebase。该应用程序设置类似于群聊,并允许用户加入。
用户获得一个密钥,然后可以将自己连接到相应的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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆