Firebase callbacks work during testing but not during runtime

时间:2017-11-02 15:39:33

标签: java android firebase callback firebase-authentication

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 ?

1 个答案:

答案 0 :(得分:0)

原因是Firebase始终从主线程调用其回调。

&#34; main&#34;我的测试中的线程称为&#34; junittestrunnerXXX&#34;。 Firebase创建了一个名为&#34; main&#34;调用回调。

在运行时,&#34; main&#34;线程是实际的&#34;主要&#34;线。如果我在上面调用get(),它就会被阻止。 Firebase会检查此线程是否存在,并且因为它已经存在,并且因为它被阻止,所以没有任何反应。