Escobar5
Escobar5

Reputation: 4082

Firebase Android: How to read from different references sequentially

In one of my Android activities I need to perform multiple queries to Firebase to finally show something to the user.

In summary, I need to check in a Users reference to check which course step he is currently in, then I need to read the contents of the Course to load it.

What I´m currently doing is that I have two nested listeners like this:

ref1.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {

         ref2.addListenerForSingleValueEvent(new ValueEventListener() {
             @Override
             public void onDataChange(DataSnapshot dataSnapshot) {
                  //do the work
             }
         }); 
    }
}); 

Is there a better way to do this queries when you need them sequentially?

Upvotes: 4

Views: 4592

Answers (2)

Steven Yeh
Steven Yeh

Reputation: 1

According to the Firebase blog: https://firebase.googleblog.com/2016/09/become-a-firebase-taskmaster-part-3_29.html

We could implement a chain of asynchronous task as the following:

public Task<ClassReturnedByTask3> wrapAllTask() {
    return Tasks.call(new Task1())
        .continueWithTask(new Task2())
        .continueWithTask(new Task3());
}

Where Task1 through Task3 are defined as:

static class Task1 implements Callable<ClassReturnedByTask1> {
    @Override
    public ClassReturnedByTask1 call() throws Exception {
        ClassReturnedByTask1 result = new ClassReturnedByTask1();
        return result;
    }
}

static class Task2 implements Continuation<ClassReturnedByTask1, Task<ClassReturnedByTask2>> {
    @Override
    public Task<ClassReturnedByTask2> then(Task<ClassReturnedByTask1> task) {
        final TaskCompletionSource<ClassReturnedByTask2> tcs = new TaskCompletionSource();

        ClassReturnedByTask1 resultFromTask1 = task.getResult();
        ClassReturnedByTask2 result = new ClassReturnedByTask2();
        tcs.setResult(result);
        return tcs.getTask();
    }
}

static class Task3 implements Continuation<ClassReturnedByTask2, Task<ClassReturnedByTask3>> {
    @Override
    public Task<ClassReturnedByTask3> then(Task<ClassReturnedByTask2> task) {
        final TaskCompletionSource<ClassReturnedByTask3> tcs = new TaskCompletionSource();

        ClassReturnedByTask2 resultFromTask2 = task.getResult();
        ClassReturnedByTask3 result = new ClassReturnedByTask3();
        tcs.setResult(result);
        return tcs.getTask();
    }
}

To execute the wrapAllTask() function, you can run:

Task<ClassReturnedByTask3> tasks = wrapAllTask();
tasks.addOnSuccessListener(new OnSuccessListener<ClassReturnedByTask3>() {
    @Override
    public void onSuccess(ClassReturnedByTask3 resultFromTask3) {
        // do something
    }
});

Upvotes: 0

JP Ventura
JP Ventura

Reputation: 5742

TL;DR

Just like Parse did with Bolts, Google also provided a task framework that implement JavaScript promises. So, instead of nesting listeners, you can create a sequence of tasks.

The result will be sent to addOnSuccessListener if all tasks execute successfully.

If any of them fail during the use case execution, the sequence will be aborted and the exception is passed to addOnFailureListener.

public Task<Course> execute() {
    return Tasks.<Void>forResult(null)
        .then(new GetUser())
        .then(new GetCourse());
}

public void updateInBackground() {
    Tasks.<Void>forResult(null)
        .then(new GetUser())
        .then(new GetCourse())
        .addOnSuccessListener(this)
        .addOnFailureListener(this);
}

@Override
public void onFailure(@NonNull Exception error) {
    Log.e(TAG, error.getMessage());
}

@Override
public void onSuccess(Customer customer) {
    // Do something with the result
}

Description

Suppose you wish to download two object of type User and Course from Firebase.

You need to create the first task of your sequence using the Tasks API. Your options are:

I prefer the first option mostly due code legibility. If you you need run the tasks in your own executor, you should use the first or second option.

Now let's create two Continuation tasks two download each one:

class GetUser implements Continuation<Void, Task<User>> {

    @Override
    public Task<User> then(Task<Void> task) {
        final TaskCompletionSource<User> tcs = new TaskCompletionSource();

        ref1.addListenerForSingleValueEvent(new ValueEventListener() {

            @Override
            public void onCancelled(DatabaseError error) {
                tcs.setException(error.toException());
            }

            @Override
            public void onDataChange(DataSnapshot snapshot) {
                tcs.setResult(snapshot.getValue(User.class));
            }

        });

        return tcs.getTask();
    }

}

and

class GetCourse implements Continuation<User, Task<Course>> {

    @Override
    public Task<Course> then(Task<User> task) {
        final User result = task.getResult();
        final TaskCompletionSource<Course> tcs = new TaskCompletionSource();

        ref2.addListenerForSingleValueEvent(new ValueEventListener() {

            @Override
            public void onCancelled(DatabaseError error) {
                tcs.setException(error.toException());
            }

            @Override
            public void onDataChange(DataSnapshot snapshot) {
                tcs.setResult(snapshot.getValue(Course.class));
            }

        });

        return tcs.getTask();
    }

}

According the documentation, call getResult() and allow the RuntimeExecutionException to propagate to propagate failure from the completed Task.

The RuntimeExecutionException will be unwrapped such that the Task returned by continueWith(Continuation) or continueWithTask(Continuation) fails with the original exception.

Upvotes: 5

Related Questions