Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactor] 개발자 모드 개선 & 네트워크 예외 처리 오류 수정 (FlowCallAdapter) #349

Merged
merged 25 commits into from
Jun 5, 2024

Conversation

dongx0915
Copy link
Member

@dongx0915 dongx0915 commented Apr 30, 2024

📌 개요

✨ 작업 내용

  • 네트워크 예외 처리 오류 수정 (FlowCallAdapter로 변경)

    • 네트워크 요청 + 예외 처리 예시를 보여드리기 위해 개발자 모드와 같이 PR 올립니다.
    • ViewModel, Service 쪽 로직 참고 바랍니다.
    • 노션 회의록
  • 🌟 개발자 모드 기능 개선 🌟

    1. 개발자 모드 디자인 수정
    2. 토큰 공유 기능 추가 (AccessToken + RefreshToken 같이 공유 가능)
    3. 서버 모드 변경시 앱 자동 재실행
    4. 서버 상태 체크 기능 추가

✨ PR 포인트

FlowCallAdapter 동작 확인

  • FlowCallAdapter를 사용했을 때 통신 잘 되는지 크로스체크 부탁드립니다~
  • 요청 성공시, 서버 에러시, 예외 발생시 등등

네트워크 예외 테스트 케이스

  • 아래 테스트 케이스로 검증하였는데, 여유 있으신 분들은 크로스체크 부탁드립니다.
  • 누락된 케이스 또는 추가로 확인해야할 부분이 있는지도 봐주시면 감사하겠습니다.
  • ( 해당 부분은 테스트 한 번 진행해서 바쁘신 분은 패스해주셔도 괜찮습니다~! )
Case 1.
  • 선행 조건 : exceptionHandler, flow catch 연산자 모두 작성하지 않은 경우
  • 예상 결과 : 크래시 발생
Case 2.
  • 선행 조건 : exceptionHandler만 작성한 경우
  • 예상 결과 : 크래시 발생 x, exceptionHandler에서 예외 잡힘
Case 3.
  • 선행 조건 : flow catch 연산자만 작성한 경우
  • 예상 결과 : 크래시 발생 x, catch 연산자에서 예외 잡힘
Case 4.
  • 선행 조건 : exceptionHandelr, flow catch 연산자 둘 다 작성한 경우
  • 예상 결과 : 크래시 발생 x, catch 연산자에서 먼저 예외 잡힘 (catch에서 처리하지 않은 예외는 exceptionHandler에서 잡힘)
📸 스크린샷/동영상
서버 상태 체크 토큰 공유 토큰 공유 결과
image image image

* 네트워크 요청에서 예외가 발생하면 flow의 catch 연산자로 잡히지 않음
* Flow로 변환되기 전에 Interceptor에서 예외가 발생하기 때문
* 따라서, Retrofit에서 Flow로 반환하도록 변경
* 네트워크 요청에서 발생하는 예외도 flow로 보내도록 수정
@dongx0915 dongx0915 requested review from leeeha, sxunea and unam98 May 1, 2024 02:50
@dongx0915 dongx0915 added REFACTOR 🧹 코드 리팩토링 동현 🐨 동현 담당 labels May 1, 2024
@dongx0915 dongx0915 self-assigned this May 1, 2024
@dongx0915 dongx0915 added the FIX 💥 버그 및 오류 해결 label May 1, 2024
Copy link
Member

@leeeha leeeha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

추가된 코드가 상당히 많네요 👀 콜 어댑터 사용 방법에 적응하는 시간이 필요할 거 같습니다 😇
고생 많으셨습니다~!!

@GET
fun checkServerStatus(
@Url url: String
): Flow<Result<ResponseServerStatus>>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서는 사용자 정의 타입 FlowResult 를 사용하지 않은 특별한 이유가 있나요??

Copy link
Member Author

@dongx0915 dongx0915 May 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 회의록에 적어둔대로 적용할 지 의견을 들어본 후 수정할 예정이라 다 고치진 않았습니다~!

import com.runnect.runnect.developer.data.service.ServerStatusService
import javax.inject.Inject

class RemoteServerStatusDataSource @Inject constructor(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전부터 느꼈지만 remote가 상위 패키지 이름이므로 클래스 앞에 매번 Remote라는 접두사는 안 붙여도 될 거 같다는 생각이 듭니다! 사소하지만 코멘트 남겨봅니다 ㅎ.ㅎ

private val clipboardManager: ClipboardManager? by lazy {
context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
}

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences_developer_menu, rootKey)
activity?.apply {
setStatusBarColor(window = window, true, R.color.white)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named argument 모든 인자에 대해 적용해주면 더 좋을 거 같습니다 : )

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 오 네네 수정해두겠습니다~!

}

private fun initUserInfo() {
val ctx: Context = context ?: return
val accessToken = ctx.getAccessToken()
val refreshToken = ctx.getNewToken()
val combinedToken = "${ApiMode.getCurrentApiMode(ctx).name} 서버\n[Access Token]: $accessToken\n\n---\n\n[Refresh Token]: $refreshToken"
Copy link
Member

@leeeha leeeha May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

귀찮으니까 기능 하나 더 만들어버리는 ㅎㅎㅎ 최고 👍

Intent.makeRestartActivityTask(component).apply {
startActivity(this)
exitProcess(0)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿!! 사용하기 더 편해졌네요ㅎㅎ

Comment on lines +36 to +44
fun checkProdServerStatus() {
val prodServerUrl = "${BuildConfig.RUNNECT_PROD_URL}/actuator/health"
checkServerStatus(prodServerUrl, _prodStatus)
}

fun checkTestServerStatus() {
val testServerUrl = "${BuildConfig.RUNNECT_DEV_URL}/actuator/health"
checkServerStatus(testServerUrl, _testStatus)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

두 함수의 동작이 굉장히 유사해서 인자를 추가하면 하나로 통일할 수 있을 거 같아요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha
오호 더 좋은 방법 생각 나신게 있다면 코드 제안 부탁드립니다~

503 -> ServerState.Degraded
else -> ServerState.Error
}.let(state::tryEmit)
}
Copy link
Member

@leeeha leeeha May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 디테일한 분기 처리 👏👏

import com.runnect.runnect.R
import com.runnect.runnect.developer.enum.ServerStatus

class ServerStatusPreference @JvmOverloads constructor(
Copy link
Member

@leeeha leeeha May 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 @JvmOverloads 이걸 붙이면 생성자 오버로딩을 자동으로 생성해주는군요!

import com.runnect.runnect.R
import com.runnect.runnect.developer.presentation.RunnectDeveloperViewModel.ServerState

enum class ServerStatus(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 기능은 어디서 쇽샥 해오신 건지.. 👍👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leeeha 서버팀 레포 구경하다가 Actuator 설정 되어있길래 바로 적용 했슴니다😎

Copy link
Contributor

@unam98 unam98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰가 너무 늦었습니다 죄송합니다 ㅎㅎ ㅜ 수고 많으셨습니다 최고~!!


private fun parseResponse(response: Response<T>): Result<T> {
val nullBodyException by lazy {
RunnectException(response.code(), ERROR_MSG_RESPONSE_IS_NULL)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RunnectException은 어떤 상황에 발생하는 exception인가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98 그냥 프로젝트 공용으로 커스텀 Exception을 만들어야할 때 쓸 목적으로 만들어 뒀습니다


private suspend fun flowApiCall(call: Call<T>): Result<T> {
return suspendCancellableCoroutine { continuation ->
runCatching {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그때 여기 runCatching으로 감싼 이유가 머라고 하셨었죵?

Copy link
Member Author

@dongx0915 dongx0915 May 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98 call.enqueue의 onFailure로 내려오지 않는 예외까지 처리하기 위함이었으나 예외가 발생하면 call.enqueue의 onFailure로 내려오네요

예외 자체를 flow로 보내도록 변경하였으니 runCatching을 굳이 안써도 될 것 같습니다!

}
})
}.onFailure {
continuation.resumeWithException(it)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 어떤 경우에 여기로 빠지는 건가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98 timeout 등의 예외가 call.enqueue에서 안잡히는 줄 알았는데 지금 확인해보니까 call.enqueue의 onFailure로 잘 내려오네요

runCatching을 제거해도 이제는 flow로 감싸져있으니까 예외가 잘 잡히네요
runCatching 제거해도 될 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98 테스트를 해보긴했는데 runCatching 없어도 크래시 발생하지 않는지 크로스 체크 부탁드립니다~

}

continuation.invokeOnCancellation {
call.cancel()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

세심함 좋아여

gson.fromJson(it, ErrorResponse::class.java)
}

val errorMessage = errorResponse?.message ?: errorResponse?.error ?: ERROR_MSG_COMMON
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기는 에러 메세지 분기를 3단으로 나눠주신 특별한 이유가 있나요?

Copy link
Member Author

@dongx0915 dongx0915 May 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98

  1. errorResponse?.message : 러넥트 서버 커스텀 에러의 메세지
  2. errorResponse?.error : Http 기본 에러 메세지

러넥트 서버의 커스텀 에러와 Http 기본 에러 형식을 모두 받을 수 있게 ErrorResponse를 작성해두었습니다.
러넥트 서버의 커스텀 에러를 우선적으로 수신하기 위해 위와 같이 분기하였습니다

Comment on lines +29 to +30
fun <R, D> FlowResult<R>.toEntityResult(mapper: (R) -> D): FlowResult<D> {
fun <R, D> Result<R>.parseResult(mapper: (R) -> D): Result<D> = when {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수 안에 함수를 넣어주신 이유가 있나요? 개인적인 생각으론 여기 안에서만 쓰인다면 굳이 함수화까진 안 해도 되지 않을까 싶어서용.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@unam98
개인적으로 가독성이 좀 더 좋다고 생각되어 내부 함수로 만들었습니다
더 깔끔한 방법이 있으면 코드 제안 부탁드립니다~

@dongx0915 dongx0915 merged commit 89150b0 into develop Jun 5, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FIX 💥 버그 및 오류 해결 REFACTOR 🧹 코드 리팩토링 동현 🐨 동현 담당
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Refactor] 개발자 모드 개선 & 네트워크 예외 처리 오류 수정 (FlowCallAdapter)
3 participants