AuthenticationManager_리펙토링_과정 - boostcampwm-2024/and04-Nature-Album GitHub Wiki
- 힐트를 사용하여 MyPageViewModel, AuthenticationManager 의존성 주입
- AuthenticationManager 리펙토링
AuthenticationManager
코드의 길이와 깊이가 복잡해지면서, 리팩터링의 필요성을 느꼈다.
현재 코드에서는 메서드의 최대 중첩(depth)이 6을 넘어가고 있으며, signInWithGoogle
메서드 하나에 모든 로직이 집중되어 있었다. 따라서 코드의 가독성을 높이고 유지 보수를 용이하게 하기 위해, 메서드를 여러 개의 기능별 함수로 분리하는 리팩터링을 계획했다.
또한 리팩터링 과정에서 AuthenticationManager.signInWithGoogle()
메서드 호출 시, 성공과 실패 여부를 MyPageScreen
에서 직접 처리하는 방식에서 개선하고자 했다. UI에서 직접적으로 성공과 실패를 확인하는 대신, ViewModel
에서 이러한 결과를 처리하고 토큰 값을 유지하도록 만들기로 계획했다.
정리하자면, AuthenticationManager.signInWithGoogle()
의 코드를 함수를 통해 분리하고, 뷰모델을 도입하여 인증 성공, 실패에 대한 처리를 관리하고자 했다.
첫 계획은 힐트를 통해 AuthenticationManager(context)
를 viewmodel에서 주입받고 사용하는 것이었다.
CredentialManger를 생성할때 인자로 넣어주는 Context가 ApplicationContext가 아닌 Activity-based Context로 넣어줘야 하는 상황이었다.
AuthenticationManager
생성자로 주입받고 있는 context를 @ApplicationContext
를 통해 주입했었다.
class AuthenticationManager @Inject constructor(
@ApplicationContext
private val context: Context
) {
하지만 앱을 실행하자 위와 같은 에러가 발생했다.
근본적인 원인으로 CredentialManger
를 생성할 때 인자로 넣어주는 context가 Activity-based context여야 한다는 것이었다.
val result = credentialManager.getCredential(
context = context, // Activity-based Context가 필요하다.
request = request
)
따라서 @ApplicationContext
대신 @ActivityContext
로 변경하여 해결하고자 했다.
class AuthenticationManager @Inject constructor(
@ActivityContext
private val context: Context
) {
두 Context는 어떤 차이가 있길래 CredentialManger
는 ActivityContext를 요구했던 것일까?
어느 부분에서 Activity Contex가 필요한 것인지 내부 코드를 확인해봤다.
위 이미지에서 getCredentialAsync()
의 인자로 넣어줘야 하는 context가 Activity Context여야 한다는 것을 찾을 수 있었다.
참고 : And04 Wiki | Android Context
이전 문제 상황에서 ApplicationContext를 ActivityContext로 고치자 빌드 과정에서 발생한 문제.
@Privide
어노테이션을 통해 의존성을 주입했음에도 발생한 에러
위에서 @ApplicationContext
대신 @ActivityContext
로 변경하자마자 안드로이드 스튜디오 빌드 과정에서 에러가 발생했다.
Stack OverFlow | Hilt error ActivityContext cannot be provided without ...
위 Stack Overflow에서 알려준 ActivityRetainedScoped
을 사용해보기도 했고, 다른 스코프로 지정해보기도 했다.
하지만 결과적으로는 해결하지 못했다.
Android Context 사용 시 Memory leck 방지를 위한 문제 해결
코드 리펙토링 | '미주 정복' 2일차 - Room 설정
그 이유는 위 참고 자료에서 찾을 수 있었다.
viewmodel의 lifecycle이 activity lifecycle보다 길기에 ActivityContext를 제공받을 수 없었던 것이었다.
다른 방법으로는 viewmodel factory에서 CredentialManger(context)
를 생성해보려고 했다.
fun <VM : ViewModel> viewModelFactory(initializer: () -> VM): ViewModelProvider.Factory {
return object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return initializer() as T
}
}
}
위와 같은 viewmodelFactory를 생성하였고, 아래와 같이 만드려고 했지만 두 번째 오류가 발생했다.
LocalContext.current
는 컴포저블 함수에서 호출해야했기에 viewmodelFactory에서는 접근할 수 없었던 것이다.
Viewmodel에서 context를 직접 넣어주는 것은 최대한 지양하려고 했음에도 다른 방법으로 해결하지 못하여 결국에는 viewmodel.signInWithGoogle(context)
메서드에 넣어주기로 결정했다.
해당 오류를 수정하고 AuthenticatioManager
코드를 함수로 분리하는 리펙토링 과정을 수행하였다.
아래의 트러블 슈팅은 리펙토링 과정이 끝난 후 발생한 에러이다.
callbackFlow 에서는 블록 제일 마지막 부분에 awaitClose()
메서드를 호출해야 한다.
하지만 내가 작성한 코드 마지막 부분에는 이미 awaitClose()
메서드를 호출했음에도 이런 문제가 발생했다.
이미 블록 제일 마지막 부분에 awaitClose()
를 호출하고 있는 상황임에도 이런 에러가 발생했다.
예외 처리를 하면서 catch 부분에서 awaitClose()
를 호출하지 않아 발생한 문제인가 싶어 모든 catch문, try 문 제일 마지막 부분, 함수 제일 마지막 부분 여러 군데에 전부 해당 메서드를 호출했음에도 문제가 계속 발생했다.
계속 코드를 살펴봤고, 짐작하는 바로는 *return*@callbackFlow
에서 awitClose()
를 호출하지 않고 바로 return 시켰기에 발생한 문제이지 않을까 하는 추측이었다.
그래서 return 전에도 메서드를 호출했음에도 에러가 사라지지 않았다.
여러 블로그글과 Stack OverFlow를 통해 finally 구문에서 해당 메서드를 호출하여 위 에러를 해결할 수 있었다.