Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: wait for firebase valueEventListener

I am trying to use Semaphore to wait for my firebase valueEventListener. I have an user info activity with 6 different fields that the user must fill out. When the user saves his/her info, I want to do an "all or nothing" type of check. Certain user info cannot be duplicated...for example user name, email, and phonenumber. I am using firebase and currently the general idea is of the format:

void saveUserInfo(){
    if(field1 exist in database){
        return;
    }
    .
    .
    .
    if(field6 exist in database){
        return;
    }

    savefield1();
    .
    .
    .
    savefield6();
}

The issue I am having is in the method that checks whether or not the value already exists in the database. Here is my current method:

   public boolean alreadyInUse(String key, String value) throws InterruptedException {

    final StringBuilder done = new StringBuilder("");
    final Semaphore semaphore = new Semaphore(0);

    mDatabase.child(key).child(value).addListenerForSingleValueEvent(new ValueEventListener() {

        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            String result = dataSnapshot.getValue(String.class);
            if(result == null){
                Log.d("WorkoutLog", "result: null");
                done.append("false");
                semaphore.release();
                return;
            }
            else if(result.equals(uID)){
                Log.d("WorkoutLog", "result: " + result.toString());
                done.append("false");
                semaphore.release();
                return;
            }
            else{
                Log.d("WorkoutLog", "result: " + result.toString());
                done.append("true");
                semaphore.release();
                return;
            }

        }

        @Override
        public void onCancelled(DatabaseError databaseError) {

        }
    });


    semaphore.acquire();
    if(done.equals("true")){
        return  true;
    }
    else if(done.equals("false")){
        return false;
    }
    else{
        Log.d("WorkoutLog", "Shouldn't be here");
        return true;
    }

}

Right now the semaphore is not releasing...Was wondering if anyone can help me out here. Without the semaphore, the return statement will fire before the firebase query can complete...

like image 318
David Yuan Avatar asked Aug 16 '16 03:08

David Yuan


2 Answers

As previously mentioned, you can use Firebase Task API presented on Google I/O 2016.

Task<?>[] tasks = new Task[] {
    saveUserName(username),
    saveFriends(friends),
    saveOtherStuff(stuff)
};

Tasks.whenAll(tasks)
    .continueWithTask(new RollbackIfFailure())
    .addOnCompleteListener(this)
    .addOnFailureListener(this);

If each step that makes a modification can run in parallel, you can create a method that returns a Task for each one of them as follows:

public Task<String> saveUserName(final String username) {
    final TaskCompletionSource<String> tcs = new TaskCompletionSource<>();

    mDatabase.child("users")
        .child(username)
        .addListenerForSingleValueEvent(new ValueEventListener() {
        @Override
        public void onDataChange(DataSnapshot dataSnapshot) {
            String username = dataSnapshot.getValue(String.class);
            if (null == username) {
                tcs.setResult(null);
            } else {
                tcs.setException(new IllegalStateException(username));
            }
        }

        @Override
        public void onCancelled(FirebaseError firebaseError) {
            tcs.setException(new IOException(TAG, firebaseError.toException()));
        }
    });

    return tcs.getTask();
}

If any of them fail, you need to rollback the operations. Since this can be threated by a single task, you can create a Continuation task:

class RollbackIfFailure implements Continuation<Void, Task<Void>> {
    @Override
    public Task<Void> then(@NonNull Task<Void> task) throws Exception {

        final TaskCompletionSource<Void> tcs = new TaskCompletionSource<>();

        if (task.isSuccessful()) {
            tcs.setResult(null);
        } else {
            // Rollback everything
        }

        return tcs.getTask();
    }
}
like image 186
JP Ventura Avatar answered Sep 28 '22 05:09

JP Ventura


The listener callback methods run on the main thread. If you call alreadyInUse() on the main thread, it will block the thread at semaphore.acquire(), preventing the callbacks from running and releasing the semaphore.

You might find this answer to a related question helpful.

like image 43
Bob Snyder Avatar answered Sep 24 '22 05:09

Bob Snyder