이번 시간엔 sealed class를 이용하여 서버에서 수신한 응답을 viewmodel로 전달하는 것을 구현하려고 합니다.
기존에 개발했던 아래 링크의 게시물 코드를 수정합니다.
위의 게시물에서는 Repository에서 exception 처리로 성공/실패을 응답했다면, 이번 시간엔 sealed class를 이용하여 성공/실패를 viewmodel로 전송하는 것을 구현하겠습니다.
그럼, 시작합니다.
1. 이번 개발의 핵심인 성공/실패 응답을 처리하는 sealed class와 함수를 구현합니다.
ApiResult.kt 파일을 생성하고, 아래와 같이 구현합니다.
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.HttpException
sealed class ApiResult<out T> {
data class Success<out T>(val value: T): ApiResult<T>()
object Empty: ApiResult<Nothing>()
data class Error(val code: Int? = null, val exception: Throwable? = null): ApiResult<Nothing>()
}
fun <T> safeFlow(apiFunc: suspend () -> T): Flow<ApiResult<T>> = flow {
try {
emit(ApiResult.Success(apiFunc.invoke()))
} catch (e: NullPointerException) {
emit(ApiResult.Empty)
} catch (e: HttpException) {
emit(ApiResult.Error(code = e.code(), exception = e))
} catch (e: Exception) {
emit(ApiResult.Error(exception = e))
}
}
여러가지 서버 API 응답에 대응을 위해서, 재네릭 타입 T를 이용하여 구현합니다.
네트워크 응답은 성공 또는 실패인데, null 체크를 할겸 Empty 를 추가했습니다.
Success 에는 서버에서 전달받은 데이터가 들어가게 되고, Error 에는 에러코드와 메시지가 전달되도록 구현합니다.
2. retrofit 의 api service interface(LoremPicsumApiService.kt) 에서 기존에 Response 클래스로 응답을 받던 것을 데이터 클래스로 받도록 api를 추가해줍니다.
@GET("/id/{imageId}/info")
suspend fun imageInfoSafeFlow(@Path("imageId") imageId: String): ResImageInfo.Response
3. repository(LoremPicsumRepository.kt)에, 위의 inteface를 이용한 함수를 추가합니다.
fun getImageInfoSafeFlow(imageId: String): Flow<ApiResult<ImageInfo>> = safeFlow {
service.imageInfoSafeFlow(imageId).mapper()
}
서버 요청 인터페이스를 실행하는 함수를 safeFlow()에 apiFunc 파라메터로 전달하여, safeFlow() 함수내에서 apiFunc 함수가 실행이 되도록 합니다.
safeFlow() 함수에서는 전달받은 apiFunc 을 호출하면서 try...catch()로 에러처리를 통해 ApiResult를 반환하게 됩니다.
4. viewmodel에서 ApiResult의 성공/실패/empty 에 대한 처리를 합니다.
fun getImageInfoSafeFlow(imageId: String) {
viewModelScope.launch {
repository.getImageInfoSafeFlow(imageId).collectLatest { result ->
when (result) {
is ApiResult.Success -> {
_imageInfo.postValue(result.value)
}
is ApiResult.Empty -> {
_errorMsg.postValue("data empty.")
}
is ApiResult.Error -> {
_errorMsg.postValue(result.exception?.message)
}
}
}
}
}
5. Activity에서도 위의 함수를 호출하여 실행합니다.
lifecycleScope.launchWhenStarted {
intent?.getStringExtra(Constants.KEY_EXTRA_IMAGE_ID)?.let {
/**
* sealed class로 성공/실패를 처리하는 방식
*/
viewModel.getImageInfoSafeFlow(it)
/**
* exception 으로 성공/실패를 처리하는 방식
*/
// viewModel.getImageInfo(it)
}
}
위의 개발 코드는 아래 Github 링크에서 확인해주세요.
'Android + Kotlin' 카테고리의 다른 글
[Android + Kotlin] 날짜/시간 <-> currentTimeMillis 변환 및 format 표시 (1) | 2022.03.29 |
---|---|
[Android Kotlin] Webview 디렉토리 충돌 관련 수정 방법 (https://crbug.com/558377) (0) | 2022.03.18 |
adb: more than one device/emulator 문제 해결하기 (0) | 2022.03.14 |
[Android + Kotlin] 아이폰의 스와이프하여 뒤로가기 기능을 구현해보자. (1) | 2022.02.18 |
[Android Kotlin] @BindingAdapter 를 사용하여 DataBinding 끝내기 (0) | 2022.02.09 |