본문 바로가기

안드로이드

안드로이드 RoomDB 예외 처리하는 방법

게시글을 통해서 배울 수 있는 것들

  1. 외부통신시 sealed class 를 활용해서 각 상황 대응하기
  2. 외부통신에서 예상치 못한 에러가 발생했을때 해결하는 다양한 방법들

 

게시글을 이해하기 위해서 필요한 지식들

  1.  코루틴에 대한 기본적인 개념
  2.  코틀린
  3.  안드로이드
  4.  RoomDB

 

기존에 RoomDB에 저장했던 데이터를 불러오는데 예상치 못하게 null을 받아오는 상황이 일어났습니다.

이번 문제를 겪으면서 생각했던 해결책과 대체로 다른 사람들은 어떤 식으로 처리하는지 알게되서 이를 서술해보도록 하겠습니다.

그리고 게시글의 본격적인 내용을 서술하기 앞서서 RoomDB에서 데이터를 받아오는 경우 뿐만이 아니라 외부 서버와 통신을 할때도 예상치 못한 데이터를 받아왔을때 예외를 처리하는 것은 매우 중요합니다. 안드로이드 개발자라면 외부 api를 Retrofit2 를 사용해서 받아오는 경우가 가장 빈번하겠죠. RoomDB나 Retrofit으로 데이터를 받아오는 경우 전부 마찬가지라는 사실을 염두해두시면 좋겠네요.

 

구글링을 통해 자료속에서도 정답을 찾았지만 안드로이드 오픈채팅방에서 많은 사람들의 의견을 물어보았고 크게 도움이 됐습니다. 서로 익명으로 소통을 하는 곳이다보니 다 모르는 사람들이지만 질문에 대해 도움을 주기 위해서 성심껏 답변해주시는게 늘 고맙네요.

 

구글링을해서 자료를 찾아보면 쉽게 정답을 찾아볼 수 있습니다. 글의 아래쪽에 가면 문제를 해결하는 방법이 적혀있습니다. 이제와서 보니까 좋은 방법은 아니였지만 어떻게 문제를 접근했는지 적어보도록 하겠습니다.

 

저는 두가지 방법을 생각했습니다.

방법1) 더미 데이터를 채워넣어서 사용한다.
override suspend fun getSavedUserLoginInfo(context: Context): LoginInfo? = withContext(Dispatchers.IO) {
    // 테스트용 더미데이터이다. loginInfo 객체가 null인 경우를 대비해서 더미데이터를 넣었다. 
    //안좋은 방법이다. (쓰지마십쇼) 
    var loginInfo: LoginInfo? = LoginInfo(0,"testr@naver.com","111111","email") 
    try {
        loginInfo = LoginUserDB.getInstance(context)?.getEventsDao()?.getUserInfo()
    } catch (e: Exception) {
        Logger.v(e.message.toString())
    }
    return@withContext loginInfo
}

 

더미 데이터를 넣는 방법은 뷰(activity, fragment)측에서 데이터를 받아왔을때 어떤 식으로 시나리오가 흘러갈지 이해하기 어렵습니다. (아마 저만 알겠죠. 같은 팀원들은 이 더미데이터를 넣게 됐을때 무슨일이 일어날지 모르고 시간이 지나면 저조차 까먹을겁니다. 굳이 이 방법을 고집하고 싶다면 Seaeld class를 활용해서 각 상황이 어떤식으로 시나리오가 흘려갈지 구현하는게 좋습니다.)

 

방법2) 리턴해야할 값을 애초에 null로 설정한다.
override suspend fun getSavedUserLoginInfo(context: Context): LoginInfo? = withContext(Dispatchers.IO) {
    var loginInfo: LoginInfo? = null
    try {
        loginInfo = LoginUserDB.getInstance(context)?.getEventsDao()?.getUserInfo()
    } catch (e: Exception) {
        Logger.v(e.message.toString())
    }
    return@withContext loginInfo
}

 

 

코틀린은 기본적으로 nullable한 상황을 지양합니다. 개발을 하다가 "아니 이거 null 로 다 때려박혀있는데 이래도 되나?" 라는 생각이 들어서 리서치를 해보기 시작했습니다. 많은 개발자분들이 각 상황별로 대응을 해주는게 좋다고 말씀해주셨습니다. 그리고 이렇게 null값을 넘기는 것 보다는 차라리 빈 리스트를 넘겨주는게 낫지 않겠냐는 의견이 지배적이였습니다. 

 

위의 코드와 제 상황을 바탕으로 사람들에게 질문을 했을때 다양한 조언을 받았습니다. 미래의 제가 보고 도움이 되기를 바라는 마음 + 이 게시글을 읽는 사람들에게 도움이 되기를 바라면서 해결책들을 종합해보겠습니다.

 

  1. 빈 리스트를 넘긴다. (위의 방법2 보다는 차라리 빈 리스트를 넘겨라. nullable한 코드보다는 그게 낫다.)
  2. dataEmptyException 하나를 정의하고 throw한뒤 뷰단에서 예외 핸들링을 해라.
  3. 메소드(getSavedUserLoginInfo)를 nullable 하게 하지말고 non-null로 처리해라. 거듭 반복해서 하는 말이지만 nullable 한 것은 안좋으니까.)
  4. sealed class 를 활용해서 각 상황 대응하기

저는 4번 방법을 채택해서 글을 작성해볼게요.

 

SignInActivityRepository.kt

interface SignInActivityRepository {
    suspend fun getSavedUserLoginInfo(context: Context): RoomResult<LoginInfo>
}

@Singleton
class SignInActivityRepositoryImpl @Inject constructor() : SignInActivityRepository {
    override suspend fun getSavedUserLoginInfo(context: Context): RoomResult<LoginInfo> {
        return try{
            val response = LoginUserDB.getInstance(context)!!.getEventsDao().getUserInfo()
            RoomResult.Success(response)
        } catch (e : Exception){
            Logger.v(e.message.toString())
            RoomResult.Error(e.message ?: "Error")
        }
    }

}

 

SignInUsecase.kt

class SignInUsecase @Inject constructor(private val signInActivityRepository: SignInActivityRepository) {
	
    ...

    suspend fun getSavedUserLoginInfo(context: Context) = flow {
        emit(signInActivityRepository.getSavedUserLoginInfo(context))
    }

}

 

LoginActivityViewModel.kt

@HiltViewModel
class LoginActivityViewModel @Inject constructor(
    private val application: Application,
    private val signInUsecase: SignInUsecase,
    private val signUpUsecase: SignUpUsecase
) : AndroidViewModel(application) {

    init{
        getSavedUserLoginInfo(application.applicationContext)
    }

	
    ...
    
    // 이메일로 로그인한 사용자의 정보를 불러오는 메소드
    private fun getSavedUserLoginInfo(context : Context) = viewModelScope.launch(Dispatchers.IO) {
        signInUsecase.getSavedUserLoginInfo(context).collect{
            Logger.v(it.toString())
        }
    }

}

 

뷰에서는 알아서 라이브데이터를 사용해서 써먹으면 될 것 같네요. 글을 쓰면서도 아직 손봐야할게 너무 많이 남았다는게 느껴져서 갈길이 멀구나 싶지만 하나씩 하나씩 해야죠 다들 힘내십쇼 화이팅.

 

참고한 블로그

https://android-dev.tistory.com/entry/AndroidKotlin-%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Room-Database-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B02

 

https://medium.com/@galcyurio/kotlin%EC%97%90%EC%84%9C%EC%9D%98-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EB%B2%95-48a5cd94a4e6

 

Kotlin에서의 예외 처리 방법

최근 몇 년간 교육 기관에서 코드 리뷰를 진행하면서 대부분의 리뷰이들이 예외 처리를 잘못 알고 구현하는 경우들을 많이 보았습니다.

medium.com

 

https://toss.tech/article/kotlin-result

 

 

 

 

 

 

'안드로이드' 카테고리의 다른 글

navigation  (4) 2024.10.01
FireBaseStorageCleanArchitecutre 예제  (0) 2024.02.29
Flow (안드로이드)  (0) 2024.02.16
Hilt를 이용한 ViewModel 객체 생성  (0) 2024.02.15
viewPager2 Indicator 예제  (0) 2024.02.01