[ERROR] java.lang.IllegalStateException: Flow invariant is violated: Emission from another coroutine is detected.

Jini.Dev·2024년 6월 20일
0

다른 코루틴에서 방출이 감지되었습니다.
FlowCollector는 스레드로부터 안전하지 않으며 동시 배출이 금지됩니다. 
이 제한을 완화하려면 'flow' 대신 'channelFlow' 빌더를 사용하십시오.

Coroutines Flow를 사용하다가 위와 같은 에러가 발생했다.

수정 전 문제가 있었던 코드
A-Fragment

onCreateView {
   lifecycleScope.launch(Dispatchers.Main) {
       viewModel.test(a).collect {data ->
           goToBActivity(data)
       }
   }
}

viewModel

fun test() = flow {
   viewModelScope.launch {
       val getSomething = async {requestApi(A)}
       val addSomething = async {equestApi(B)}

       getSomething.await().let { data -> doSomething()}

       val BData = addSomething.await()?.walletAccount
       if (BData != null) {
          emit(BData)
       }
   }
}.flowOn(Dispatchers.IO).catch { handleThrowable(it) }

뷰모델의 test()에서 두 API를 동시 처리하기 위해 CoroutineScope내에서 async, await를사용한뒤 그 결과값을 A-Fragment에서 사용해야했기에 flow로 값을 받아 사용하는 형태의 코드였다.

A-Fragment에서 CorountinesScope가 시작되었는데 viewModel에서 또 실행, emit을 해서 생긴 에러다.

발생 한 에러 로그에서 flow' 대신 'channelFlow' 빌더를 사용하라고 되어있어 channelFlow를 사용하하고 나와있어서 수정해보았다.

'channelFlow' 빌더로 수정한 코드
A-Fragment 는 변경사항 없음
viewModel

fun test() = callbackFlow {
   viewModelScope.launch {
       val getSomething = async {requestApi(A)}
       val addSomething = async {equestApi(B)}

       getSomething.await().let { data -> doSomething()}

       val BData = addSomething.await()?.walletAccount
       if (BData != null) {
          trySend(BData)
       }
   }
}.flowOn(Dispatchers.IO).catch { handleThrowable(it) }

데이터를 flow밖으로 보내야해서 callbackFlow{}를 사용해 trySend()로 데이터를보냈다.

이렇게..
수정했는데...
안됐다..
에러는 안나는데 A-Fragment에서 실행되야할 goToBActivity(data) 부분이 실행이 안되는것이다.

검색결과
해당 함수는 close()가 호출될 때 까지 채널을 열어둔 상태로 기다린다고 한다.

공식문서 추가설명
Suspends until either 'onCompleted'/'onApiError' from the callback is invoked or flow collector is cancelled (e.g. by 'take(1)' or because a collector's coroutine was cancelled). In both cases, callback will be properly unregistered.

최종 수정된 viewModel

fun test() = callbackFlow {
   viewModelScope.launch {
       val getSomething = async {requestApi(A)}
       val addSomething = async {equestApi(B)}

       getSomething.await().let { data -> doSomething()}

       val BData = addSomething.await()?.walletAccount
       if (BData != null) {
          trySend(BData)
       }
   }
   awaitClose()
}.flowOn(Dispatchers.IO).catch { handleThrowable(it) }

awaitClose()를 추가하니 원하는대로 동작이 되었다.

profile
정신 차려보니 개발자가 되어있었다.

2개의 댓글

comment-user-thumbnail
2024년 6월 20일

와 정말 유익해요 저도 도움이 되었어요
그리고 정말 많이 공부하신 거 같아요 ^^b

1개의 답글