Acquire viewModelScope in helper class used in ViewModel

Viewed 290

Hi I have a question: what would be the best practice to pass viewModelScope to the helper/manager class used in viewModel.

For example, I would like to create an AccountManager class that would take care of the account, perform some async task operations, read/write from the database etc... I want to expose the result through Kotlin Flow and collect it in my ViewModel, and for that reason I need to access viewModelScope (so I can emit values), because creating a new scope doesn't seem like a very good idea.

I'm using Hilt dependency injection, and I would like to inject account manager as a dependency in ViewModel. Also, one thing worth mentioning is that I would like to use this Account Manager in more than one place in my app, because it's encapsulating some repetitive operations. There is a concern that if I create AccountManager as a Singleton I could create memory leak if I pass a particular viewModelScope to it... So basically I need your opinion what would be the best solution for this?

1 Answers

First you need to understand that the emit and collect are called in the same CoroutineScope. Please look at the source code. It is not necessary to transfer the scope to anywhere explicitly

public suspend inline fun <T> Flow<T>.collect(crossinline action: suspend (value: T) -> Unit): Unit =
    collect(object : FlowCollector<T> {
        override suspend fun emit(value: T) = action(value)
    })

The collect method is suspend. Internally, it runs both the flow block and the collect block. Therefore, all the work of sending and receiving data will be done in the coroutine, in which we call the collect() method.

class TestViewModel(private val manager: Manager) : ViewModel() {

    private fun load() =
        viewModelScope.launch(IO) {
            manager.doWork1().collect {

            }
        }

    private fun load2() =
        viewModelScope.launch {
            manager.doWork2().collect {
            }
        }
}

Manager class

class Manager {

    fun doWork1() = flow {
        for (k in 1..10) {
            val t = newValue(k)
            emit(t)
        }
    }

    suspend fun doWork2() = flow {
        withContext(IO) {
            for (k in 1..10) {
                val t = newValue(k)
                emit(t)
            }
        }
    }

    private suspend fun newValue(param: Int) {
        delay(5000)
        Random(param).nextInt()
    }
}
Related