Home > Net >  Flow.collect blocking the main thread
Flow.collect blocking the main thread

Time:05-01

I've the following code that seems to blocking the main thread even though the flow is called on IO coroutine. I'm a kotlin and flow noob. What am I doing wrong here that's blocking the main thread?

Repository:

fun observeData(): Flow<Data> {
  return flow {
     //third party api is getting data from a ContentProvider
     ThirdPartyApi.getData().map { convertFromExternalModelToDataModel(it) }
     .collect {
         emit(it)
     }
  }
}

ViewModel:

fun updateUI() {
   scope.launch(Dispatchers.IO) {
     repository.observerData().collect {
       withContext(Dispatchers.Main) {
          textView.text = data.name
       }
     }
   }
}

Upon running the following code it I see logs from Android Choreographer "Skipped 200 frames. App is going too much work on main thread"

CodePudding user response:

To collect the data stream with Kotlin Flows as they're emitted, use collect. And as collect is a suspending function, it needs to be executed within a coroutine. It takes a lambda as a parameter that is called on every new value. Since it's a suspend function, the coroutine that calls collect may suspend until the flow is closed.

And you shouldn't be updating your UI inside a ViewModel.

In this case we collect flow inside an activity's lifecycle scope that is main safe and has activity's lifecycle awareness.

And to make our service or repository to execute in a different CouroutineContext, use the intermediate operator flowOn.

flowOn changes the CoroutineContext of the upstream flow, meaning the producer and any intermediate operators applied before (or above) flowOn.

The downstream flow (the intermediate operators after flowOn along with the consumer) is not affected and executes on the CoroutineContext used to collect from the flow.

ViewModel:

fun getData():Flow<Data> = repository.observeData() // Execute on the io dispatcher
// flowOn affects the upstream flow ↑
.flowOn(Dispatchers.IO)
// the downstream flow ↓ is not affected
.catch { exception -> // Executes in the consumer's context
    emit(Data())
}

Activity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    lifecycleScope.launch { // Consumer's context
        viewModel.getData().collect { // Suspended
            textView.text = data.name // Collect on consumer's context
        }
    }
}
  • Related