12

I want to define these two collections in Cloud Firestore. Some sample data has been shown below:

Playlists:

  • name: "playlist1"
  • songCount: //To be calculated on real time

Songs:

  • title: "song1"
  • playlistId: "playlist1"

  • title: "song2"

  • playlistId: "playlist1"

  • title: "song3"

  • playlistId: "playlist1"

Further, in my Android app, I want to show separate lists of all playlists and all songs. However, while displaying the list of all playlists, I want to show the name of each playlist alongwith the number of songs contained in each, based on the value in "songCount".

Please advise what sort of query I need to make in Cloud Firestore to achieve this objective. Is there a JOIN function available or would I have to put the Songs collection in a loop and count the ones having playlistId as "playlist1", and then repeat it for all the playlists that are to be presented in the list of playlists? I want to have a smarter solution but couldn't find one on the internet.

Any help would be a great help. Thanks for your time.

Shahood ul Hassan
  • 745
  • 2
  • 9
  • 19
  • Possible duplicate of [Firestore Query - Joining two collections](https://stackoverflow.com/questions/51110662/firestore-query-joining-two-collections) – LundinCast Oct 08 '18 at 07:25
  • @LundinCast I checked it out before posting but it doesn't provide a definitive answer or may be I couldn't get it right. If u have understood it, pl explain it here. Thanks – Shahood ul Hassan Oct 08 '18 at 09:50

1 Answers1

12

Is there a Join function available?

Unfortunately, there is no JOIN query in Firestore. Queries in Firestore are shallow: they only get items from the collection that the query is run against. There is no way to get documents from a top-level collection and other collections or sub-collections in a single query. Firestore doesn't support queries across different collections in one go. A single query may only use properties of documents in a single collection. So the most simple solution I can think of would be to use a database structure that looks similar to this:

Firestore-root
   |
   --- playlists (collection)
   |     |
   |     --- playListId (document) //The unique id of the play list
   |           |
   |           --- name: "playlist1"
   |
   --- songs (collection)
        |
        --- playListId (document) //The same as the above playListId
               |
               --- playListSongs
                     |
                     --- songId
                          |
                          --- title: "song1"

In order to display all playlists, just attach a listener to the playlists reference and get all playlist objects. If you want to get all songs that correspond to a particular playlist, just attach a listener to songs\playListId\playListSongs and get all song objects.

Regarding the count of all songs that correspond to a playlist, I recommend you see my answer from this post, where I have explained what you can achieve this. So according to the last part of my answer, your Firebase Realtime Database structure should look like this:

Firebase-Realtime-Database-root
    |
    --- playlists
          |
          --- playListIdOne: numberOfSongs
          |
          --- playListIdTwo: numberOfSongs

Edit:

I can't say I've understood it fully especially because the first answer involves Firestore and the second involves Firebase Realtime Database.

I gave you this solution because if you want to use Cloud Firestore to count and update elements every time a song is added or deleted, you'll be charged by Firestore for every write/delete operation. Firebase Realtime Database has another pricing plan, so you'll not be charged for that. See Firestore pricing Please read again till the end, my answer from this post.

Also, I couldn't really get the procedure for calculating the count of songs.

This is how you can get the number of songs from Firestore and write it to the Firebase Realtime Database:

rootRef.collection("songs").document(playListId).collection("playListSongs")
.get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
    @Override
    public void onComplete(@NonNull Task<QuerySnapshot> task) {
        if (task.isSuccessful()) {
            Log.d("TAG", task.getResult().size() + "");
            DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
            DatabaseReference playListIdRef = rootRef.child("playlists").child(playListId);
            playListIdRef.setValue(task.getResult().size());
            
        } else {
            Log.d(TAG, "Error getting documents: ", task.getException());
        }
    }
});

And this is how you can read:

DatabaseReference rootRef = FirebaseDatabase.getInstance().getReference();
DatabaseReference playListIdRef = rootRef.child("playlists").child(playListId);
ValueEventListener valueEventListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
            long playListIdOne = dataSnapshot.getValue(String.Long);
            Log.d(TAG, "playListIdOne: " + playListIdOne);
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        Log.d(TAG, databaseError.getMessage());
    }
};
playListIdRef.addListenerForSingleValueEvent(valueEventListener);

Secondly, as per your suggested structure, how would I get a list of all songs across all playlists?

In this case, you should create another collection, named allSongs. The additional collection in your database should look like this:

Firestore-root
   |
   --- allSongs
         |
         --- songId
              |
              --- //song details

This practice is called denormalization and is a common practice when it comes to Firebase. For a better understanding, I recommend you see this video, Denormalization is normal with the Firebase Database. It is for Firebase Realtime Database but same rules apply to Cloud Firestore.

Also, when you are duplicating data, there is one thing that you need to keep in mind. In the same way you are adding data, you need to maintain it. In other words, if you want to update/delete an item, you need to do it in every place that it exists.

Keeping in mind that one same song can be found in more than one playlists.

It doesn't matter because each song has a different id.

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • Thanks for such a detailed answer. I can't say I've understood it fully especially because first answer involves FireStore and the second involves Firebase realtime db. Also, I couldn't really get the procedure for calculating count of songs. Secondly, as per your suggested structure, how would I get a list of all songs across all playlists, keeping in mind that one same song can be found in more than one playlists? Thanks again for taking time. – Shahood ul Hassan Oct 08 '18 at 13:01
  • Shouldn't it be rootRef.collection("songs").document(playListId).collection("playListSongs").get() ? – Shahood ul Hassan Oct 08 '18 at 13:54
  • Yes, you're right. My bad, sorry about that. Just updated my answer. Is it ok now? – Alex Mamo Oct 08 '18 at 13:55
  • Yep. Ur answer has cleared some confusions but has created some too. And that's only because I'm totally new to Firebase. My actual objective is something else. After a lot of reading on the internet and watching many videos, I thought Firebase/Firestore might help me fulfill it....now I think otherwise. I believe it is the time when I take a step back and again focus on my actual objective. I'll post it as a new question on SO and request all the experts especially u to guide me accordingly. Your answers may help me achieve it without resorting to Firebase. – Shahood ul Hassan Oct 08 '18 at 15:17
  • Sure but if u don't mind, I would like to keep it open for another couple of days in the hope of getting answers from some other users too. – Shahood ul Hassan Oct 08 '18 at 15:34
  • Is there a way I can engage in a chat with u regarding the new question or would u prefer it to be asked on SO? – Shahood ul Hassan Oct 08 '18 at 16:12
  • Please add the question on stackoverflow.com. – Alex Mamo Oct 08 '18 at 16:18
  • Please have a look at this https://stackoverflow.com/questions/52707493/how-to-implement-cloud-sharing-and-collaboration-features-in-android-app – Shahood ul Hassan Oct 08 '18 at 17:41
  • 1
    I'll take a look and if I'll know the answer, I'll write it to you. – Alex Mamo Oct 08 '18 at 18:18