skip to Main Content

I am trying to write a database query in Cloud Firestore in Kotlin.

The request itself is working but I am having some problems with the async workflow.

My goal is to make a database request that fetches a lot of data and puts this into an object.
Once this is complete for all objects, the normal workflow of the app should continue. (because I need to display some of this data in my UI).

My database structure:
I have a collection of users
-> Each user has a collection of their own recipes.
-> In each recipe there is a collection of Steps to complete and a collection of ingredients

My goal is to get the data and show it on my app screen (code below), but with my current code, the app starts the request and continues to show the app screen (which means there is no data loaded up to this point).

Is there a way to make my database request more efficient (as I have 3 requests per recipe) and also to make it work async? So that when the data gets loaded the UI will recognize a change and show this to the UI later on or the UI waits till all the requests are complete?

Sorry if this is a basic question about coroutines, but I am pretty new to this topic and could not find any info in the Firebase documentation.

My Code:

in My Main Activity:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        loadRecipes()//should load the information from the Database
        initUiElements()//should show the Information in a Recycler View
    }

-> load Recipies calls the following Class with the loadloadRecipes() function

Class: FirebaseRecipes:

fun loadRecipes(){

        //First DB Request to get the Recipe Names and Durations
        Log.d("FirebaseRecipes", "loadRecipes()")
        db.collection("users").document(userID.toString()).collection("Recipes").get()
            .addOnSuccessListener { result ->
                for (document in result) {
                    val duration = document.data["duration"].toString()
                    val name = document.data["name"].toString()
                    var recipe = Recipe(name, duration)
                    RecipesList.add(recipe)

                    //Second DB Request to get the Ingredients
                    db.collection("users").document(userID.toString()).collection("Recipes").document(document.id).collection("ingredients").get()
                        .addOnSuccessListener { result ->
                            for (document in result) {
                                val amount = document.data["amount"].toString()
                                val name = document.data["name"].toString()
                                val barcode = document.data["unit"].toString()
                                val ingredient = Ingredient(name, amount, barcode)
                                recipe.ingredients.add(ingredient)
                            }
                        }

                    //Third DB Request to get the Steps
                 db.collection("users").document(userID.toString()).collection("Recipes").document(document.id).collection("steps").get()
                        .addOnSuccessListener { result ->
                            for (document in result) {
                                val step = document.data["text"].toString()
                                recipe.steps.add(step)
                            }
                        }
                }
            }
    }

2

Answers


  1. With this question, what you need to do is learn about

    StateFlow

    ShareFlow

    LiveData

    Flow

    Suspend Function

    To know how to save, listen, handle data asynchronous

    If you didn’t learn about them before just learn

    For example you have State Flow in ViewModel

        data class StateFragment(
            val isLoading : Boolean = false
            val message: String ?= null
            val data : <What Ever> ?= null
        )
    
        private val _saveData = MutableStateFlow(StateFragment())
        val saveData = _saveData.asStateFlow()
     
        // you have a function request list data
        function requestList() {
    
           _saveData.value = StateFragmnet(isLoading = true) 
           //Make progress or something listen isLoading at UI to make user know data is loading
    
           //Request data list (function request data list should be a suspend function so code below will never excuted until request done)
    
           //If request data list finished, just update then UI can listen that data
           //Now listen data at UI, like about data list, progress stop loading when isLoading = true
           _saveData.value = StateFragment(data= newData, isLoading = false)
    
        }
    

    Then listen data at UI like this

     lifecycleScope.launch {
          viewmodel.saveData.collect { // data will be get everytime here if _saveDate updated }
       }
    

    Data will never be destroyed if you do other things

    Login or Signup to reply.
  2. The base limit in Firebase seems to be 100 items within a collection

    There is no such limitation in Firestore. You can add as many documents as you want in a collection, even billions.

    I want all the recipes for one user.

    So if you think that the maximum number of recipes a user can have is 100, then you can add all of them in a single document, including the ingredients and the steps. In this way, you’ll have to pay for a single-read operation. However, there is a limitation when it comes to how much data you can store in a single document. You can only store data up to 1 MiB.

    If you think that you can get over this limitation, which I doubt, then instead of creating one document, you might consider creating two, or even three. So there is no need in this case to create a separate document for each recipe, ingredient, and step. All the data cand be stored as object in an array.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search