๐คฆโ๏ธ ๋ฌธ์ ์ํฉ
API์ ์ฃผ์์ ์ค๋ฅ๊ฐ ์๊ธฐ๊ฑฐ๋ ํน์ ๋ค๋ฅธ ์ค๋ฅ ๋ฐ์ ์์ ์ฑ์ด ๋น์ ์์ ์ผ๋ก ์ข ๋ฃ๋๋ ํ์์ ๋ฐฉ์งํ๊ธฐ ์ํด Coroutine์ ์์ธ์ฒ๋ฆฌ ์ฝ๋๊ฐ ํ์ํ์ต๋๋ค.
// Coroutines Error ๊ฐ ๋ฐ์ํ๋ ์ฝ๋
override suspend fun getApi1FromApi2(): CheckAppVersionResponse {
val deferred = CoroutineScope(Dispatchers.IO).async {
val checkAppType = async {
getCheckAppType().appType
}
getCheckAppVersion(checkAppType.await())
}
return deferred.await()
}
๐งจ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ ์ด์ (์ถ์ )
launch
ํ์ ์ ๋น๋๋ก ์์ฑ๋ ์ฝ๋ฃจํด์ด ๋ฃจํธ ์ฝ๋ฃจํด์ธ ๊ฒฝ์ฐ, ์ฝ๋ฃจํด ๊ณ์ธต ๊ตฌ์กฐ ๋ด์์ ๋ฐ์ํ ์์ธ๋ ๋ฃจํธ ์ฝ๋ฃจํด์ ์์ธ ํธ๋ค๋ฌ์ ์ํด ์ฒ๋ฆฌ๋ฉ๋๋ค.- Exception์ ์ธ๋ถ๋ก ์ ํ(propagation) ์ํจ๋ค.
async
ํ์ ์ ๋น๋๋ก ์์ฑ๋ ์ฝ๋ฃจํด์ด ๋ฃจํธ ์ฝ๋ฃจํด์ธ ๊ฒฝ์ฐ, ์ฝ๋ฃจํด ๊ณ์ธต ๊ตฌ์กฐ ๋ด์์ ๋ฐ์ํ ์์ธ๋ ํธ์ถ์์๊ฒ ์์ธ ์ฒ๋ฆฌ๋ฅผ ๋งก๊ธฐ๋ฉฐ ๋ฃจํธ ์ฝ๋ฃจํด์ ์์ธ ํธ๋ค๋ฌ๋ ๋์ํ์ง ์์ต๋๋ค. (ํธ์ถ์๊ฐawait()
,receive()
๋ฑ์ ๋ฐ์ดํฐ ์์ ํจ์๋ฅผ ํธ์ถํ ๋ ์์ธ๊ฐ ๋ฐ์ํจ)- Exception์ ๋ ธ์ถ(exposing) ์ํจ๋ค.
async
๋ก ์์ฑํ ์ฝ๋ฃจํด์ async
์์ await()
ํ๋ฉด ์์ธ๊ฐ ๋ฐ์ํ ์ฝ๋ฃจํด์์ ์ฒ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์ฑ์ด ๋น์ ์์ ์ผ๋ก ์ข
๋ฃ๋๋ ํ์์ ๋ฐ์ํ์ง ์์ต๋๋ค.
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.async {
Log.d(TAG, "${comApiRepository.getApi1FromApi2()}")
}
}
ํ์ง๋ง ๋ง์ฝ async
๋ก ์์ฑํ ์ฝ๋ฃจํด์ ์์ธ ํธ๋ค๋ฌ๋ฅผ ๋ฌ์ง ์์ launch
๋ธ๋ญ์์ await()
ํ๋ฉด ์์ธ๊ฐ ์ ํ๋๋ฉด์ ์ฑ์ด ๋น์ ์์ ์ผ๋ก ์ข
๋ฃ๋๋ ํ์์ด ๋ฐ์ํฉ๋๋ค.
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
Log.d(TAG, "${comApiRepository.getApi1FromApi2()}")
}
}
--------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.rit.dynamicurlapi, PID: 6508
kotlin.KotlinNullPointerException: Response from com.rit.data.remote.service.ApiService.checkAppType was null but response body type was declared as non-null
at retrofit2.KotlinExtensions$await$2$2.onResponse(KotlinExtensions.kt:43)
at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:161)
at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:535)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@f3485f9, Dispatchers.Main.immediate]
๐ ํด๊ฒฐ ๋ฐฉ๋ฒ
1. CoroutineExceptionHandler ์ฌ์ฉ
/**
* MainViewModel.kt
*/
private val _state = MutableLiveData<State>()
val state: LiveData<State>
get() = _state
// CoroutineExceptionHandler ์์ฑ
protected val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
Log.e("CoroutineExceptionHandler", "${exception.message}")
when(exception) {
is SocketException -> _state.postValue(State.BAD_INTERNET)
is HttpException -> _state.postValue(State.PARSE_ERROR)
is UnknownHostException -> _state.postValue(State.WRONG_CONNECTION)
else -> _state.postValue(State.FAIL)
}
}
private fun getData() {
// ๋ฃจํธ ์ฝ๋ฃจํด์ CoroutineExceptionHandler ๋ฌ๊ธฐ
viewModelScope.launch(coroutineExceptionHandler) {
Log.d(TAG, "${comApiRepository.getApi1FromApi2()}")
}
}
2. runCatching ๊ตฌ๋ฌธ
2-1. ๋ฃจํธ ์ฝ๋ฃจํด์ด async
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.async {
Log.d(TAG, "${comApiRepository.getApi1FromApi2ErrorHandlingResult()}")
}
}
/**
* RepositoryImpl.kt
*/
override suspend fun getApi1FromApi2ErrorHandlingResult(): CheckAppVersionResponse? {
val checkAppType = CoroutineScope(Dispatchers.IO).async { getCheckAppType().appType }
val checkAppVersion = CoroutineScope(Dispatchers.IO).async { getCheckAppVersion(checkAppType.await()) }
return kotlin.runCatching {
checkAppVersion.await()
}.onSuccess {
Log.d(this::class.java.name, "$it")
}.onFailure {
Log.e(this::class.java.name, "${it.message}")
}.getOrThrow()
}
2-2. ๋ฃจํธ ์ฝ๋ฃจํด์ด launch
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
Log.d(TAG, "${comApiRepository.getApi1FromApi2ErrorHandlingResult()}")
}
}
/**
* RepositoryImpl.kt
*/
override suspend fun getApi1FromApi2ErrorHandlingResult(): CheckAppVersionResponse? {
val checkAppType = CoroutineScope(Dispatchers.IO).async { getCheckAppType().appType }
val checkAppVersion = CoroutineScope(Dispatchers.IO).async { getCheckAppVersion(checkAppType.await()) }
return kotlin.runCatching {
checkAppVersion.await()
}.onSuccess {
Log.d("Repository", "$it")
}.onFailure {
Log.e("Repository", it.message.toString())
}.getOrNull()
}
2-3. ๋ฃจํธ ์ฝ๋ฃจํด์์ runCatching
/**
* MainViewModel.kt
*/
private fun coroutinesErrorHandling() {
viewModelScope.launch {
kotlin.runCatching {
comApiRepository.getApi1FromApi2()
}.onSuccess {
Log.d(TAG, "$it")
}.onFailure {
Log.e(TAG, "${it.message}")
}
}
}
๊ทธ ์ธ
์๋ฃ ์ฐธ์กฐ
https://june0122.tistory.com/20
https://medium.com/naverfinancial/์ฝ๋ฃจํด-์์ธ-๋ค๋ฃจ๊ธฐ-acb5b91dad0a
https://uchun.dev/runCatching์-์ด์ฉํ-kotlin์์-exception์ฒ๋ฆฌ-๋ฐฉ๋ฒ/
'Programando > Android' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Android/Kotlin] Fragment State ์ ์ง (0) | 2023.01.11 |
---|---|
[Android/Kotlin] ์๋๋ก์ด๋ 12 SplashScreen ๋์ํ๊ธฐ (0) | 2022.06.29 |
[Android/Kotlin] Hilt (0) | 2022.05.22 |
[Android/Kotlin] RxJava์ RxJava๋ฅผ ์ด์ฉํ EventBus ๊ตฌํ (0) | 2022.05.04 |
[Android/Kotlin] ์๋ฐฉํฅ ๋ฐ์ธ๋ฉ(Two-way DataBinding) (0) | 2022.05.02 |