kotlinandroid-roomkotlin-coroutinesdagger-hiltandroid-mvvm

Why Kotlin's collect function doesn't terminate itself?


When I download info from a DataBase the collect function doesn’t stop emission (Kotlin's collect function doesn't terminate itself.)

I have this entity class:

@Entity
data class ShareEntity(
    @PrimaryKey(autoGenerate = false)
    val secId: String,
    val shortName: String,
    val fullName: String = "",
    val isFavorite: Boolean
)

The function to get all the shares for specific by “isFavorite” in the Dao looks like this:

@Dao
interface DataBaseDao {
………..
@Query("SELECT * FROM ShareEntity WHERE isFavorite =:isFavorite")
fun getAllSharesByIsFavorite(isFavorite: Boolean): Flow<List<ShareEntity>>
}

In the DataBaseDao there are many other functions that insert some data into ShareEntity entity.

For working with the Dao interface the DataBaseRepository and its implementation DataBaseRepositoryIm were made:

interface DataBaseRepository {
………..
fun getAllSharesByIsFavorite(isFavorite: Boolean): Flow<List<ShareEntity>>
}
class DataBaseRepositoryIm(
private val dataBaseDao: DataBaseDao
) : DataBaseRepository{
………..
override fun getAllSharesByIsFavorite(isFavorite: Boolean): Flow<List<ShareEntity>> {
 return dataBaseDao.getAllSharesByIsFavorite(isFavorite)
}
}

In my NewsScreenViewModel1 I have a function that loads all the Shares by “isFavorite”

@HiltViewModel
class NewsScreenViewModel1 @Inject constructor(
    private val dataBaseRepository: DataBaseRepository,
    private val repository: ApiRepository,
) : ViewModel(){

suspend fun downloadListOfSharesByIsFavorite(isFavorite: Boolean = true){
 val shares = dataBaseRepository.getAllSharesByIsFavorite(isFavorite).collect{ it:List<ShareEntity> ->
 println("NewsScreenVM1: allShares is with isFavorite = $isFavorite are $it")
 println("NewsScreenVM1: the size of shares is ${it.size}")
 }
 println("NewsScreenVM1: Now we are outside of the collect function")
}
}

In the composable NewsScreen I start the function with LauncheEffect:

@Composable
fun NewsScreen(
    NewsScreenViewModel1: NewsScreenViewModel1 = hiltViewModel()
) {
    LaunchedEffect(key1 = Unit) {
        NewsScreenViewModel1.downloadListOfSharesByIsFavorite()
    }

}

The problem is that collect function doesn’t terminate itself or in other words it doesn’t stop. In the LogCat I see:

“NewsScreenVM1: allShares is with isFavorite = true are [ShareEntity(secId=ALRS, shortName=АЛРОСА ао, fullName=, isFavorite=true), ShareEntity(secId=AGRO, shortName=AGRO-гдр, fullName=, isFavorite=true), ShareEntity(secId=AFLT, shortName=Аэрофлот, fullName=, isFavorite=true), ShareEntity(secId=FEES, shortName=Россети, fullName=, isFavorite=true)]”

“NewsScreenVM1: the size of shares is 4”

And there is no: "NewsScreenVM1: Now we are outside of the collect function” which means the collect function hasn’t stopped its emission and is still running.

What did I do wrong? Why I can’t execute lines of the code after collecting some data from the database?

I tried to run the flow on a different coroutine scope like this:

fun downloadListOfSharesByIsFavorite(isFavorite: Boolean = true){
 viewModelScope.launch { 
val shares = dataBaseRepository.getAllSharesByIsFavorite(isFavorite).collect{ it:List<ShareEntity> -> 
println("NewsScreenVM1: allShares is with isFavorite = $isFavorite are $it")
println("NewsScreenVM1: the size of shares is ${it.size}")
}
println("NewsScreenVM1: Now we are outside of the collect function")
}
}

But the message, 'NewsScreenVM1: Now we are outside of the collect function,' appears right before all the calculations, which is not what I want. I want it to be the last massage.

When I use async function couple with .await() I don’t get the last message either as the flow never stops. Here is the code:

suspend fun downloadListOfSharesByIsFavorite(isFavorite: Boolean = true) {
        val asyncValue = viewModelScope.async {
            var size = 0
            val shares = dataBaseRepository.getAllSharesByIsFavorite(isFavorite)
                .collect { it: List<ShareEntity> ->
                    println("NewsScreenVM1: allShares is with isFavorite = $isFavorite are $it")
                    println("NewsScreenVM1: the size of shares is ${it.size}")
                    size = it.size

                }
            size
        }
            println("NewsScreenVM1: Now we are outside of the collect function. The list size is ${asyncValue.await()}")
        }

Solution

  • That's the point of using a Flow. It doesn't end because it's waiting for DB updates. Every time the list changes in DB, you will get the new updated list in collect. By design it is not meant to end: https://developer.android.com/codelabs/basic-android-kotlin-training-intro-room-flow

    If what you want is to just query the current list once, no need for a Flow nor a collect call. Just use a suspend function that returns a List.