androidkotlinfirebase-realtime-databaseandroid-livedata

Android recursive call of type Flow not called


I want make a recursive call when if condition is true as you can see inside this method, but is does not work.

I tried to replace this getWordsRequestsCount() not to be a callbackFlow, but my custom callback, but it gave no effect but anyway it is not probably a key here.

If you need anything more just ask me about it.

So, how to solve that? Thank you in advance.

fun getWordsRequests(motherTongue: String,
                     foreignLanguage: String,
                     userId: String,
                     isNextPortion: Boolean,
                     isToShowPb: MutableLiveData<Boolean>): Flow<ArrayList<WordLoader>> =
    callbackFlow {

        val ref = database.getReference("words_requests").child(motherTongue)
            .child(foreignLanguage)
        var postListener: ValueEventListener? = null

        getWordsRequestsCount(motherTongue, foreignLanguage, userId).collect {
            postListener = object : ValueEventListener {

                val list = ArrayList<WordLoader>()

                override fun onCancelled(error: DatabaseError) {
                    trySend(list)
                    isToShowPb.value = false
                }

                override fun onDataChange(dataSnapshot: DataSnapshot) {

                    if (it > 0) {
                        for (ds in dataSnapshot.children) {
                            if (ds.child("user_id").value != userId) {
                                val word = Word.prepare(ds, motherTongue, foreignLanguage)

                                val wordLoader = WordLoader(word)
                                list.add(wordLoader)

                                val isMore = Util.isMoreForMore(it, list.size)

                                wordLoader.isMore = isMore
                                val index = list.indexOf(wordLoader)

                                list[index] = wordLoader
                            }
                        }

                        isToShowPb.value = false

                        if (list.size < 2 && list.size < it) {
                            lastWordRequest += list.size
                            getWordsRequests(motherTongue, foreignLanguage, userId, isNextPortion, isToShowPb)
                        }
                    }

                    if (list.isNotEmpty()) {
                        val newLastAddingId = list.last().word.id!!.toDouble()
                        lastWordRequest = if (newLastAddingId > lastWordRequest) newLastAddingId else lastWordRequest
                    }

                    trySend(list)
                    list.clear()
                }
            }

            if (isNextPortion) {
                lastWordRequest++
            }

            ref.orderByChild("id").startAt(lastWordRequest)
                .limitToFirst(TOTAL_ELEMENTS_IN_LIST + 1)
                .addListenerForSingleValueEvent(postListener)

            if (it == 0) {
                clearValues()
                isToShowPb.value = false
            }

        }

        awaitClose {
            ref.orderByChild("id").startAt(lastWordRequest)
                .limitToFirst(TOTAL_ELEMENTS_IN_LIST + 1)
                .addListenerForSingleValueEvent(postListener!!)
        }
    }

EDIT - minimal example

fun getWordsRequests(motherTongue: String,
                     foreignLanguage: String,
                     userId: String): Flow<ArrayList<WordLoader>> =
    callbackFlow {

        val ref = database.getReference("words_requests").child(motherTongue)
            .child(foreignLanguage)
        var postListener: ValueEventListener? = null

        getWordsRequestsCount(motherTongue, foreignLanguage, userId).collect {
            postListener = object : ValueEventListener {

                val list = ArrayList<WordLoader>()

                override fun onCancelled(error: DatabaseError) {
                    trySend(list)
                }

                override fun onDataChange(dataSnapshot: DataSnapshot) {
                    if (it > 0) {
                        for (ds in dataSnapshot.children) {
                            if (ds.child("user_id").value != userId) {
                                // ADDING TO LIST
                            }
                        }

                        if (list.size < 2 && list.size < it) {
                            getWordsRequests(motherTongue, foreignLanguage, userId)
                        }
                    }

                    trySend(list)
                    list.clear()
                }
            }

            ref.orderByChild("id").startAt(lastWordRequest)
                .limitToFirst(TOTAL_ELEMENTS_IN_LIST + 1)
                .addListenerForSingleValueEvent(postListener)
        }

        awaitClose {
            ref.orderByChild("id").startAt(lastWordRequest)
                .limitToFirst(TOTAL_ELEMENTS_IN_LIST + 1)
                .addListenerForSingleValueEvent(postListener!!)
        }
    }

Solution

  • Every call to getWordsRequests() creates new flow (with its own Firebase listener).
    Calling it inside itself just creates another cold flow, it doesn’t magically continue emitting values into your current flow.

    fun getWordsRequests(
        motherTongue: String,
        foreignLanguage: String,
        userId: String,
        isNextPortion: Boolean,
        isToShowPb: MutableLiveData<Boolean>
    ): Flow<List<WordLoader>> = callbackFlow {
    
        val ref = database.getReference("words_requests")
            .child(motherTongue)
            .child(foreignLanguage)
    
        // launch a coroutine inside callbackFlow to handle recursive fetching
        launch {
            getWordsRequestsCount(motherTongue, foreignLanguage, userId).collect { totalCount ->
    
                suspend fun fetchPortion(startId: Double) {
                    val list = mutableListOf<WordLoader>()
                    val listener = object : ValueEventListener {
                        override fun onCancelled(error: DatabaseError) {
                            trySend(emptyList())
                            isToShowPb.value = false
                        }
    
                        override fun onDataChange(snapshot: DataSnapshot) {
                            for (ds in snapshot.children) {
                                if (ds.child("user_id").value != userId) {
                                    val word = Word.prepareAssociation(ds, motherTongue, foreignLanguage)
                                    val wordLoader = WordLoader(word)
                                    wordLoader.isMore = Util.isMoreForMore(totalCount, list.size + 1)
                                    list.add(wordLoader)
                                }
                            }
    
                            if (list.isNotEmpty()) {
                                val newLastId = list.last().word.id!!.toDouble()
                                lastWordRequest = maxOf(lastWordRequest, newLastId)
                            }
    
                            // send to the flow
                            trySend(list.toList())
                            isToShowPb.value = false
    
                            // 🔁 Recursive part → call again if condition holds
                            if (list.size < 2 && list.size < totalCount) {
                                fetchPortion(lastWordRequest + 1)
                            }
                        }
                    }
    
                    ref.orderByChild("id")
                        .startAt(startId)
                        .limitToFirst(TOTAL_ELEMENTS_IN_LIST + 1)
                        .addListenerForSingleValueEvent(listener)
                }
    
                if (totalCount == 0) {
                    clearValues()
                    isToShowPb.value = false
                    trySend(emptyList())
                } else {
                    if (isNextPortion) lastWordRequest++
                    fetchPortion(lastWordRequest)
                }
            }
        }
    
        awaitClose {
            // nothing special to clean up here because we use singleValueEvent
        }
    }