달달한 스토리

728x90
반응형

출처 핀터레스트

 

flow에는 종료 시점에 후처리 할 수 있는 onCompletion이 존재한다.

 

우선 코드의 상황은 다음과 같다.

 

useCase를 통해서 서버에 요청하여 list를 불러오는 viewModel의 코드 부분이다.

 

class MainViewModel(private val useCase: UseCase) : ViewModel() {

    private val _blogList  = MutableStateFlow<UiState>(UiState.Loading)
    val blogList : StateFlow<UiState> = _blogList.asStateFlow()

    private var isLast = false

    init {
        viewModelScope.launch {
            flow{
                emit(useCase.getBlogList().toUiBlogList())
//                throw NetworkErrorException()
            }.onCompletion { e ->
                Log.d("onCompletion", e.toString())
                if(isLast) {
                    Log.d("onCompletion", "isLast")
                }
            }.catch { e ->
                Log.d("catch Error", e.toString())
                _blogList.emit(UiState.Failure(e))
            }.collect {
                //서버에서 요청한 리스트가 빈값이라면,
                if(it.isNotEmpty()) {
                    Log.d("collect Success", it.toString())
                    _blogList.emit(UiState.Success(it))
                } else {
                    isLast = true
                }
            }
        }
    }
}

 

onCompletion 실행 로직

 

viewModel이 처음 생성이 되고,

 

flow를 하나 만들어준다.

 

useCase를 통해 list를 호출하고, 원하는 모델로 매핑해줘서 emit 해준다.

 

자체 flow에서 emit(방출)을 했기 때문에, 자체 flow의 collect로 수신받게 된다.

 

서버에서 만약 불러온 값이 빈 값이거나 Null이 아니라면,

 

다음과 같이 blogList Stateflow의 값을 넘겨주어 ui단으로 결괏값을 emit 해준다.

 

하지만 Null이라면 isLast 불리언을 true로 바꾸어준다.

 


그러면 collect에서 emit 된 데이터가 모두 수집이 된 후 호출이 되는

 

onCompletion이 실행이 된다.

 

그리고 isLast가 true여서 다음과 같은 로그가 실행이 된다.

 

Log.d("onCompletion", "isLast")

 

catch 실행 로직

 

다음 볼 로직은 catch이다.

 

코드에서 주석으로 표시된

throw NetworkErrorException()

을 flow에서 예외로 던져주면, 가장 먼저 실행되는 것이 바로

 

collect이다. emit된 데이터들은 무사히 수집하게 되고,

 

이어서 onCompletion이 실행이 된다.

 

여기서 try/catch 구문과는 다른 onCompletion의 장점이 부각되는데,

 

onCompletion에서도 Throwable객체를 받을 수 있다.

 

flow가 종료되고, 어떤 예외가 일어났는지 종료 시점에 받을 수 있다는 것이다.

 

그 후에 catch 구문이 실행이 된다.

 

물론 catch 구문에서 Throwable 객체를 얻을 수 있다.

 

순서는 collect -> onCompletion -> catch 순이다.

 

 

 

음.. 그러면 catch 구문이 굳이 필요 없지 않은가라는 생각을 할 수 있다.

 

하지만 만약 아래 코드처럼

 

catch 구문을 없애고 실행은 하게 된다면,

 

init {
    viewModelScope.launch {
        flow{
            emit(useCase.getBlogList().toUiBlogList())
            throw NetworkErrorException()
        }.onCompletion { e ->
            Log.d("onCompletion", e.toString())
            if(isLast) {
                Log.d("onCompletion", "isLast")
            }
        }.collect {
            //서버에서 요청한 리스트가 빈값이라면,
            if(it.isNotEmpty()) {
                Log.d("collect Success", it.toString())
                _blogList.emit(UiState.Success(it))
            } else {
                isLast = true
            }
        }
    }
}

 

앱이 죽어버리는 것을 볼 수 있다.

 

예외 처리를 해주는 부분이 catch부분이기 때문에,

 

예외 상황을 생각한다면, catch 블록은 존재해야 한다.

 

 

 

아래는 위의 내용을 한 번에 정리한 것이다.

 

onCompletion : onCompletion은 collect에서 flow의 수집을 모두 완료했을 때 호출되며,
catch에서 일어난 예외에 대한 값을 받을 수 있어서, 어떤 예외가 발생했는지 종료 시점에 알 수 있다.

 

 catch : try catch에서 catch에 해당하는 부분이다. 에러 사항을 Throwable 객체로 받을 수 있다. 예외처리를 하는 부분이다.

 

 flow에서 NetworkErrorException() 예외가 일어났을 때

실행되는 순서는 collect -> onCompletion -> catch입니다.

728x90
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading