[Android] viewmodel에서 코루틴 _ 예외처리하기 ( coroutine exception handler 이용)

이유정·2022년 2월 19일
0
post-thumbnail

참고한 사이트 :

들어가기 전에

코루틴을 이용, 예외 처리를 해야할 때 _ 현재까지는 try_catch 구문을 사용하여 예외를 처리해왔다.

    fun requestFriendList()  = viewModelScope.launch(Dispatchers.IO) {
        try {
            _friendList.postValue(RetrofitBuilder.friendService.getFriendList(getToken()))
        } catch (e: Throwable) {
            //여기서 예외처리.
        }
    }

하지만 해야하는 예외처리는 HttpException 외에도, UnknownHostException이나 SocketException 등 다양한 예외가 있었고, 이를 모든 request 함수마다 일일이 넣는 것은 중복도 많고, 귀찮았다.

때문에 코루틴 예외처리를 검색하다가 coroutine exception handler를 알게 되었다. 그리고 이를 모든 viewModel에 동일하게 들어가야하기에 BaseViewModel을 이용하여 이를 정의하기로 하였다.

Coroutine Exception Handler

말 그대로, 코루틴에서 Exception이 생겼을 때 이를 처리하는 핸들러이다. 또한 위 코드에서 사용한 Dispatcher.IO와 마찬가지로 둘 다 CoroutineContext를 인터페이스로 가지는 Coroutine Context이기도 하다.

+) Dispatchers = 코루틴이 실행될 스레드풀을 관리하는 관리자로서, 스레드풀을 통해 내부 스레드의 부하 상태에 맞춰 코루틴을 분배해주는 역할을 함.
Dispatchers.IO는 안드로이드에 기본적으로 생성되어있는 Dispatcher로서 네트워크 작업 실행에 최적화되어 있다.

// coroutine exception Handelr
 protected val exceptionHandler = CoroutineExceptionHandler{ _, exception ->
        Log.d("**********PRINT_COROUTINE_EXCEPTION", exception.throwable.stackTrace.toString())
        
    }
    
// 사용 _ 
fun requestFriendList()  = viewModelScope.launch(exceptionHandler) {
       _friendList.postValue(RetrofitBuilder.friendService.getFriendList(getToken()))
 }
    

코드를 보면 coroutine context 자리에 먼저 선언해둔 exceptionHandler를 넣어준 상태로, 통신을 하다 예외가 발생한 다면 로그창에 stackTrace라 출력될 것이다.
또한 이 exceptionHandler의 경우, 여러 job에 붙일 수 있는데, 이 점을 이용, when()을 이용하여, 하나의 exceptionHandler에서 상황에 맞게 여러 에러를 처리할 수도 있다.

내 코드


base viewmodel : 
	// 예외 난 거 저장하는 변수 ( 예외랑, enum 클래스로 예외 이름 붙인거 가져다 사용 ) 
    private val _fetchState = MutableLiveData<Pair<Throwable, FetchState>>()
    val fetchState : LiveData<Pair<Throwable, FetchState>>
        get() = _fetchState

    //코루틴 예외처리 핸들러
    protected val exceptionHandler = CoroutineExceptionHandler{ _, throwable ->
        throwable.printStackTrace()
        
        when(throwable){
            is SocketException -> _fetchState.postValue(Pair(throwable, FetchState.BAD_INTERNET))
            is HttpException -> _fetchState.postValue(Pair(throwable, FetchState.PARSE_ERROR))
            is UnknownHostException -> _fetchState.postValue(Pair(throwable, FetchState.WRONG_CONNECTION))
            else -> _fetchState.postValue(Pair(throwable, FetchState.FAIL))
        }
    }
    
 viewmodel :
 fun requestFriendList()  = viewModelScope.launch(exceptionHandler) {
       _friendList.postValue(RetrofitBuilder.friendService.getFriendList(getToken()))
 }
    
 activity : 
 	// viewmodel에서 예외를 저장한 fetchState를 옵저빙 -> 각 에러에 맞게 toast 메세지 띄우기
 	viewModel.fetchState.observe(this){
            var message = ""
            when( it.second){
                FetchState.BAD_INTERNET-> {
                    message = "소켓 오류 / 서버와 연결에 실패하였습니다."
                }
                FetchState.PARSE_ERROR -> {
                   val error = (it.first as HttpException)
                    message = "${error.code()} ERROR : \n ${error.response()!!.errorBody()!!.string().split("\"")[7]}"
                }
                FetchState.WRONG_CONNECTION -> {
                    message = "호스트를 확인할 수 없습니다. 네트워크 연결을 확인해주세요"
                }
                else ->  {
                    message = "통신에 실패하였습니다.\n ${it.first.message}"
                }

            }

            Log.d("********NETWORK_ERROR_MESSAGE : ", it.first.message.toString())
            Toast.makeText(this, message, Toast.LENGTH_LONG).show()
        }
        
        

나의 경우, 어차피 fetchState와 exceptionHandler 변수의 경우 모든 viewModel에서 사용하는 공통의 코드이기 때문에 baseViewModel을 만들어서 viewModel에서 이를 상속하도록 만들어 사용하였다.

사실 HttpException이 아닌 경우에는 exception 자체를 저장하고 있을 필요는 없지만, httpException의 경우, 아래와 같이 서버에서 보내준 에러코드와 에러메세지를 같이 띄워주고 싶어 Pair를 이용해 함께 저장하게 되었다. ( throwable만 보내주어, observe에서 처리하는게 더 나은 걸까, 어떤게 더 좋을지 고민 중... )

{
    "status": 400,
    "success": false,
    "message": "로그인 실패"
}

주저리 ) 다른 exception의 경우 exception.message를 하게되면 우리가 흔히 알고 있는 java.lang.NullPointerException: Attempt to invoke virtual method ... 하고 stackTrace가 출력되지만, httpException의 경우는 exception.message를 하게되면 위의 "로그인 실패"처럼 서버에서 보내준 message 값이 출력되게 됨.

전에 이 서버가 준 에러 메세지를 retrofit2 _ coroutine을 통해 출력하려했는데 exception이나 throwable로 접근하면 이게 안되서, 어떻게 할지 끙끙 알았던 기억이 있어서 기록.
HttpException은 코드랑 메세지에 접근이 가능하다!!!

profile
개인 공부 블로그

0개의 댓글