기존에 방법은
(1) 각 viewModel 과 activity(혹은 fragment) 에서 반복되는 코드가 계속 등장하다는 점에서 불편했다.
(2) 또한 에러 dialog 에 대한 처리가 각 activity(혹은 fragment) 마다 흩어져있어서 일괄적인 관리가 힘들었다.
이러한 불편함을 해결할 수 있는 방법은 Resource<T>
패턴콰 상속을 이용하는 방법이다.
이러한 방법은 LoadingDialog 를 띄우는 방법에도 이용할 수 있어서 굉장히 유용한 방법이다.
open class BaseActivity: AppCompatActivity() {
lateinit var errorDialog: ErrorDialog
fun errorHandle(errorCode: Int, msg: String) {
...
}
}
상속이라는 개념은 잘 알고 있으면서 프로그래밍에서 잘 사용하지 않았는데, 이 레퍼런스를 찾고나서 반성하게 되었다.
Resource<T>
패턴여러 레퍼런스를 찾다가 발견했는데, 에러 핸들링 뿐만 아니라 viewModel에서 사용되는 코드 자체를 크게 줄여줘서 만족도가 높았다. 내가 찾은 레퍼런스는 Room과 함께 사용하는 거였는데, Retorofit에 맞춰서 HttpCode 를 위한 변수를 추가했다.
data class Resource<out T>(var status: ResourceStatus,
val data: T?,
val message: String?,
val code: Int?) {
companion object{
fun <T> success(data: T?): Resource<T> {
return Resource(ResourceStatus.SUCCESS, data, null, 200)
}
fun <T> error(msg: String, data: T?, code: Int): Resource<T> {
return Resource(ResourceStatus.ERROR, data, msg, code)
}
fun <T> loading(data: T?): Resource<T> {
return Resource(ResourceStatus.LOADING, data, null, null)
}
fun <T> reset(data: T?): Resource<T> {
return Resource(ResourceStatus.RESET, data, null, null)
}
}
}
이러한 Resource 객체가 RetorofitAPI 코드를 통해 반환된 결과값을 갖도록 해주면 LiveData와 연계해서 사용하면 아주 코드가 깔끔해졌다.
class AuthRepo @Inject constructor(
private val retrofitApi: AuthAPI
): AuthRepoInterface, ResourceUtil() {
override suspend fun leave(): Resource<SimpleResponse> {
return try {
val response = retrofitApi.leave()
val resultCode: Int? = if (response.isSuccessful) response.body()!!.resultCode else null
generalResponseHandling(resultCode, response.body()?.message, response)
} catch (e: Exception) {
e.printStackTrace()
val message = e.message ?: "null"
Resource.error(message, null, -1)
}
}
}
open class ResourceUtil {
protected fun <T> generalResponseHandling(resultCode: Int?,
message: String?,
response: Response<T>
): Resource<T> {
return when {
resultCode == 200 -> {
Resource.success(response.body()!!)
}
resultCode != null -> {
Resource.error(message!!, null, resultCode)
}
else -> {
Resource.error("서버와 통신에 실패했습니다", null, -1)
}
}
}
}
@HiltViewModel
class MyConfigViewModel @Inject constructor(
private val authRepo: AuthRepoInterface
): ViewModel() {
private val _leaveState = MutableLiveData<Resource<SimpleResponse>>()
val leaveState: LiveData<Resource<SimpleResponse>> get() = _leaveState
fun exit() {
viewModelScope.launch(Dispatchers.IO) {
_leaveState.postValue(authRepo.leave())
}
}
}
viewModel 의 코드에서 볼 수 있듯이, 그냥 적절한 liveData 에 postValue 해주기만 하면 된다. 그 이후의 처리는 ResourceSatate 의 결과값에 따라 달라진다. 아래와 같을 것이다.
viewModel.leaveState.observe(this) {
when (it.status) {
ResourceStatus.SUCCESS -> {
...
viewModel.resetLeaveState()
}
ResourceStatus.ERROR -> {
errorHandle(it.code!!, it.message!!)
viewModel.resetLeaveState()
}
ResourceStatus.LOADING -> {
....
}
ResourceStatus.RESET -> {}
}
}
viewModel 에서 errorOccur 라는 라이브데이터를 만들고, 이를 activity(혹은 fragment) 에서 observe 하며 에러 메시지를 출력하는 방법을 사용했다.