4

I'm creating an Android app that uses Firebase Firestore to store some data. Since collection group queries are not yet supported in Firestore, in order to build some of the result sets that I need, I have to do a Query followed by multiple DocumentReference.get() calls to achieve the desired end result.

My implementation is as follows:

PlaylistFacade.java

public static Task<QuerySnapshot> GetPlaylistSubscriptionsByOwner(FirebaseFirestore db, @NonNull String ownerUserId)
{
    Query playlistRef = db.collection("playlistSubscriptions")
            .whereEqualTo("ownerId", ownerUserId);

    return playlistRef.get();
}

public static Task<DocumentSnapshot> GetPlaylist(FirebaseFirestore db, @NonNull String playlistId)
{
    return db.collection("playlists").document(playlistId).get();
}

PlaylistActivity.java

PlaylistFacade.GetPlaylistSubscriptionsByOwner(mFirestore, this.GetCurrentUser().getUid())
            .continueWithTask(new Continuation<QuerySnapshot, Task<List<Task<?>>>>() {
                @Override
                public Task<List<Task<?>>> then(@NonNull Task<QuerySnapshot> task) throws Exception {
                    List<Task<DocumentSnapshot>> tasks = new ArrayList<>();
                    for(DocumentSnapshot doc:task.getResult().getDocuments())
                    {
                        PlaylistSubscription ps = doc.toObject(PlaylistSubscription.class);
                        ps.setId(doc.getId());
                        tasks.add(PlaylistFacade.GetPlaylist(mFirestore, ps.getPlaylistId()));
                    }
                    return Tasks.whenAllComplete(tasks);

                }
            })
            .addOnCompleteListener(this, new OnCompleteListener<List<Task<?>>>() {
                @Override
                public void onComplete(@NonNull Task<List<Task<?>>> task) {
                    if(task.isSuccessful()){
                        List<Task<?>> tasks = task.getResult();
                        List<Playlist> playLists = new ArrayList<>();
                        int errorCount = 0;
                        for(Task<?> docTask : tasks)
                        {
                            if(docTask.isSuccessful())
                            {
                                DocumentSnapshot ds = (DocumentSnapshot)docTask.getResult();
                                Playlist pl = ds.toObject(Playlist.class);
                                pl.setId(ds.getId());
                                playLists.add(pl);
                            }
                            else
                            {
                                Crashlytics.logException(docTask.getException());
                                errorCount++;
                            }
                        }

                        if(errorCount > 0)
                            Toast.makeText(PlaylistActivity.this, "Encountered " + errorCount + " errors.", Toast.LENGTH_LONG).show();
                        else
                            Toast.makeText(PlaylistActivity.this, "Success", Toast.LENGTH_LONG).show();
                    }
                    else
                    {
                        Crashlytics.logException(task.getException());
                        Toast.makeText(PlaylistActivity.this, task.getException().getMessage(), Toast.LENGTH_LONG).show();
                    }
                }
            });

The above works just fine, but I'm curious if there may be a better way to do it.

My questions are as follows:

  1. Is chaining the Task objects using List<> the ideal approach? Am I giving up any potential performance by using this approach?
  2. Will using the Task chain still allow me to take advantage of Firebase's ability to pipeline requests?
  3. Will my use of Tasks.whenAllComplete() allow me to conditionally accept failure of some or all results, or does Firebase's pipelining cause errors to propagate across requests such that I should really just use Tasks.whenAllSuccess() and avoid the need check success of each individual request?
  4. Response times of my implementation seem fine on small result sets. Am I likely to get better performance as my result set grows if I build my result set in a Cloud Function before returning it to my app instead?
  5. At what complexity of Firestore actions should I really be using an Executor as demonstrated in the DocSnippets.java sample on the deleteCollection(...) function?
  6. Would using a Transaction to bundle requests ever net me any performance gains? Performance implications of doing reads inside a transaction aren't discussed in the documentation.
  7. Any news of when collection group queries will be available in Firestore? In time for Google IO, perhaps?

Thank you for your time.

Other helpful resources for those who end up here with similar questions:

Doug Stevenson's Firebase Blog Post on Task Wiring

HondaGuy
  • 1,251
  • 12
  • 29

0 Answers0