Search code examples
kotlinandroid-roomkotlin-flow

Is the collect function of Flow a block function in Kotlin?


I hope to delete records by Id list and relative files with Room.

I find the files can be deleted and the records can't be deleted when I run Code A

I find the records can be deleted when I run Code B.

It seems that Log.e("My","Fire") is not launched in Code A. I don't know why?

Q1: What's wrong with Code A ?

Q2: Is the collect a block function when I use Flow?

Q3: Is fun getByID(id:Int): Flow<RecordEntity?> good design? maybe it should be fun getByID(id:Int): RecordEntity? .

Code A

class RecordRepository @Inject constructor(
    private val mRecordDao:RecordDao
) {   
    override fun getByID(id:Int): Flow<MRecord?> =  mRecordDao.getByID(id).map {it?.let{ModelMapper.entityToModel(it) } }
 

    override suspend fun deleteRecord(idList: List<Int>) = withContext(Dispatchers.Default) {
        idList.forEach{
            getByID(it).collect {
               it?.let {
                   val filename = it.soundFileName
                   deleteFileOrFolder(filename)
               }
            }
        }
        Log.e("My","Fire")
        mRecordDao.deleteRecord(idList)
    }

}

@Dao
interface  RecordDao {
    @Query("SELECT * FROM record_table where id=:id")
    fun getByID(id:Int): Flow<RecordEntity?> 

    @Query("delete from record_table where id in (:idList)")
    suspend fun deleteRecord(idList: List<Int>)
}

Code B

class RecordRepository @Inject constructor(
    private val mRecordDao:RecordDao
) {   
    override fun getByID(id:Int): Flow<MRecord?> =  mRecordDao.getByID(id).map {it?.let{ModelMapper.entityToModel(it) } }


    override suspend fun deleteRecord(idList: List<Int>) = withContext(Dispatchers.Default) { 
        mRecordDao.deleteRecord(idList)
    }

}

//The same

Added Content:

To Arpit Shukla: Thanks!

I find many query samples to return Flow data with Room using Code C.

I think Code D is enought and it's simple

I'm confused why I need to use Flow in Room, you know I only want the entities once mostly, could you tell me?

Code C

@Dao
interface  RecordDao {
    @Query("SELECT * FROM record_table ORDER BY createdDate desc")
    fun listRecord():  Flow<List<RecordEntity>>
}

Code D

@Dao
interface  RecordDao {
    @Query("SELECT * FROM record_table ORDER BY createdDate desc")
    suspend  fun listRecord(): List<RecordEntity>
}

Solution

  • collect function suspends forever. It never resumes. So any code you put after collect will never be executed. This is the problem with Code A.

    If you want to make code A work, you should collect each flow in a separate coroutine using the launch function.

    override suspend fun deleteRecord(idList: List<Int>) = withContext(Dispatchers.Default) {
        idList.forEach {
           launch {
               getByID(it).collect {
                   it?.soundFileName?.let { deleteFileOrFolder(it) }
               }
           }
        }
        Log.e("My","Fire")
        mRecordDao.deleteRecord(idList)
    }
    

    Also, you need not switch Dispatchers in the code, Room does that automatically.

    Edit: If you need to get the list of RecordEntity only once, code D is a better approach. Flow is required when we need to observe the changes in the query result. For example, while displaying a list of RecordEntity in the UI, we would want to update the UI if the data in database changes and for that we can return Flow from Dao.

    For your particular case, you can do this:

    @Dao
    interface  RecordDao {
        @Query("SELECT * FROM record_table where id=:id")
        suspend fun getByID(id:Int): RecordEntity?
    }
    
    override suspend fun deleteRecord(idList: List<Int>) {
        idList.forEach { id ->
            getByID(id)?.soundFileName?.let { deleteFileOrFolder(it) }
        }
        Log.e("My","Fire")
        mRecordDao.deleteRecord(idList)
    }