[feat] #71 마이페이지 UI 수정 및 API 연동#79
Conversation
토큰이 업데이트될 때 기존 토큰 캐시가 삭제되어야하기 때문에 현재 ViewModel 에서
class TokenRepositoryImpl @Inject constructor(
private val authCacheManager: AuthCacheManager,
) {
override suspend fun saveTokens(...) {
authCacheManager.invalidateTokenCache()
...
}
// clearToken 도 동일
} |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In
`@core/common/src/main/java/com/neki/android/core/common/kakao/KakaoAuthHelper.kt`:
- Around line 14-29: The code force-unwraps token.idToken in both
UserApiClient.instance.loginWithKakaoTalk and loginWithKakaoAccount which can
cause NPE if idToken is null and also misses the case where error==null &&
token==null; refactor by extracting a single callback handler (e.g.,
handleKakaoLoginResult(token, error, onSuccess, onFailure)) used by both
loginWithKakaoTalk and loginWithKakaoAccount that: 1) checks if error != null ->
call onFailure(error.message ?: "카카오 로그인에 실패했습니다."), 2) else if token?.idToken
!= null -> call onSuccess(token.idToken), 3) else -> call onFailure with a clear
message about missing idToken (e.g., "idToken is null; ensure OIDC/openid scope
is enabled"); this removes duplication and avoids using token.idToken!!.
In
`@feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageContract.kt`:
- Line 12: The initial profileImageState is set to
EditProfileImageType.OriginalImageUrl("") but fetchInitialData's onSuccess
doesn't update it, so the UI won't reflect the server image; update the
ViewModel's fetchInitialData onSuccess reduce block to also set
profileImageState to EditProfileImageType.OriginalImageUrl(user.profileImageUrl)
(i.e., in the reduce { copy(...) } call alongside isLoading and userInfo) so the
ViewState's profileImageState is synchronized with the fetched user data.
In
`@feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt`:
- Around line 146-149: The current MyPageViewModel branch calls
uploadProfileImageUseCase(uri = uri) when isProfileImageChanged is true but
casts profileImageState with as? EditProfileImageType.ImageUri which yields null
for EditProfileImageType.Default, causing an unintended null URI; update the
logic in MyPageViewModel so you explicitly branch on profileImageState (check
for EditProfileImageType.ImageUri vs EditProfileImageType.Default) — when
ImageUri present call uploadProfileImageUseCase with that non-null uri, when
Default either skip calling uploadProfileImageUseCase or call it with an
explicit sentinel/flag meaning “reset to default” and add a brief comment
documenting the chosen behavior (or, if null is intended, add a clarifying
comment explaining that uploadProfileImageUseCase(uri = null) denotes
default-reset).
- Around line 52-55: The ClickBackIcon branch sets profileImageState to
EditProfileImageType.OriginalImageUrl but the EditProfileScreen's LaunchedEffect
doesn't handle that case, so update the LaunchedEffect to handle
EditProfileImageType.OriginalImageUrl by setting displayProfileImage =
uiState.profileImageState.url (reference: MyPageIntent.ClickBackIcon,
profileImageState, EditProfileImageType.OriginalImageUrl, displayProfileImage,
LaunchedEffect in EditProfileScreen); also treat empty string URLs as null
before assigning (or map "" -> null) so AsyncImage's model ?: fallback will use
the fallback drawable when state.userInfo.profileImageUrl is empty.
In
`@feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt`:
- Around line 85-95: displayProfileImage is initialized with remember {
mutableStateOf(uiState.userInfo.profileImageUrl) } so it never updates when
userInfo.profileImageUrl changes, and the LaunchedEffect ignores the
OriginalImageUrl case; update the logic so OriginalImageUrl sets
displayProfileImage from the latest uiState.userInfo.profileImageUrl (or make
the remembered state depend on uiState.userInfo.profileImageUrl by using
remember(uiState.userInfo.profileImageUrl) { mutableStateOf(...) }) and in the
LaunchedEffect(uiState.profileImageState) handle
EditProfileImageType.OriginalImageUrl by assigning displayProfileImage =
uiState.userInfo.profileImageUrl so the UI reflects server-loaded image updates.
In
`@feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/ProfileSettingScreen.kt`:
- Around line 46-57: The logout/unlink callbacks currently only call
navigateToLogin on onSuccess, causing the app to hang if kakaoAuthHelper.logout
or kakaoAuthHelper.unlink fails after tokenRepository.clearTokens() ran; update
the handlers in ProfileSettingScreen so both onSuccess and onFailure call
navigateToLogin (and still log the error in onFailure via Timber.e), i.e.,
change the onFailure lambdas for kakaoAuthHelper.logout and
kakaoAuthHelper.unlink to call navigateToLogin() after logging the error to
ensure the user is always routed to the login screen.
🧹 Nitpick comments (3)
core/common/build.gradle.kts (1)
13-13:api대신implementation으로 변경 검토
KakaoAuthHelper의 public API는 Kakao SDK 타입을 노출하지 않고String,Unit,Context만 사용합니다.api로 선언하면core/common에 의존하는 모든 모듈의 컴파일 클래스패스에 Kakao SDK가 불필요하게 노출되어, 빌드 시간 증가와 의도치 않은 의존성 누출이 발생할 수 있습니다.- api(libs.kakao.user) + implementation(libs.kakao.user)core/common/src/main/java/com/neki/android/core/common/kakao/KakaoAuthHelper.kt (1)
6-8:Context보유에 대한 메모리 누수 고려
KakaoAuthHelper가Context를 필드로 보유하고 있는데, 호출부(LoginScreen.kt)에서remember { KakaoAuthHelper(context) }로 생성하고 있어 Activity Context가 캡처됩니다. 현재 구조에서는 Composable 생명주기와 함께 해제되므로 큰 문제는 아니지만, 향후 이 헬퍼가 ViewModel이나 싱글턴에서 사용될 경우 Activity 누수 위험이 있습니다.context가login()에서만 사용되므로 필드 대신 메서드 파라미터로 전달하는 방식도 고려해 볼 수 있습니다.feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt (1)
182-197: 회원 탈퇴 실패 시 사용자 피드백이 없습니다.
onFailure에서Timber.e(it)로 로그만 남기고isLoading = false로 전환하지만, 사용자에게는 아무런 피드백이 없습니다. 탈퇴는 중요한 작업이므로 실패 시 최소한 토스트나 스낵바를 표시하는 것이 좋습니다.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/main/MyPageViewModel.kt`:
- Around line 182-197: withdrawAccount's onFailure only clears loading and logs
the error, so the user gets no feedback; update the onFailure block to call
postSideEffect with an appropriate MyPageEffect (e.g., MyPageEffect.ShowError or
MyPageEffect.ShowToast) carrying a user-facing message (or the
throwable.message) and keep Timber.e(it) and reduce { copy(isLoading = false) }
as is; reference withdrawAccount, onFailure, postSideEffect, and MyPageEffect
when making the change so the UI can display the error to the user.
- Around line 152-174: The onSuccess handler of userRepository.getUserInfo()
currently only posts MyPageEffect.PreloadImageAndNavigateBack when
isProfileImageChanged is true, so when only the nickname changed
(isNicknameChanged==true and isProfileImageChanged==false) no navigation effect
is emitted; update the onSuccess block in MyPageViewModel.kt so that after
reducing state you post MyPageEffect.PreloadImageAndNavigateBack when
isProfileImageChanged is true, otherwise if isNicknameChanged is true post
MyPageEffect.NavigateBack (ensure you reference the existing flags
isProfileImageChanged and isNicknameChanged and the effects
MyPageEffect.PreloadImageAndNavigateBack / MyPageEffect.NavigateBack in the
userRepository.getUserInfo() onSuccess path).
🧹 Nitpick comments (1)
feature/mypage/impl/src/main/java/com/neki/android/feature/mypage/impl/profile/EditProfileScreen.kt (1)
80-81:EditProfileScreen의 가시성을internal로 변경하는 것을 권장합니다.
EditProfileRoute는internal로 선언되어 있지만,EditProfileScreen은public입니다. 모듈 외부에서 직접 사용될 필요가 없다면internal로 제한하는 것이 캡슐화에 더 적합합니다.♻️ 수정 제안
-fun EditProfileScreen( +internal fun EditProfileScreen(
|
위에서 말씀해주셨던 프로필 이미지 변경 로직 아이디어 반영했습니다. d0f2a1e 갤러리에서 선택한 프로필 이미지와 기존 상태를 동시에 표현하기 위해 덕분에 ViewModel 내 Context 제거할 수 있었고, 자연스럽게 동작하게끔 개선할 수 있었습니다! 다만, reduce와 ImageRequest가 병렬로 진행될 시 이미 로딩다이어로그는 화면에서 제거되었지만 바로 뒤로가지 못하고, 그래서 프로필 이미지 변경 후 NavigateBack 이펙트 발생을 제거했고, 근데 이렇게 수정하니 닉네임만 변경했을 때 뒤로가지 못해서 닉네임만 변경한 경우를 구분해 |
이해했습니다. 제안해주신 방향이 더 좋겠네요! 반영했습니다. b6062cb |
45c1d16 to
b6062cb
Compare
[feat] #71 마이페이지 UI 수정 및 API 연동
🔗 관련 이슈
📙 작업 설명
🧪 테스트 내역 (선택)
📸 스크린샷 또는 시연 영상 (선택)
KakaoTalk_Video_2026-02-04-23-43-21.mp4
KakaoTalk_Video_2026-02-04-23-43-15.mp4
KakaoTalk_Video_2026-02-04-23-45-57.mp4
KakaoTalk_Video_2026-02-04-23-50-31.mp4
KakaoTalk_Video_2026-02-04-23-52-16.mp4
KakaoTalk_Video_2026-02-04-23-54-36.mp4
💬 추가 설명 or 리뷰 포인트 (선택)
레전드 고봉밥 죄송합니다...
HttpClient속성 설정 중 RefreshToken이 만료되어 로그인 화면으로 이동할 때BearerAuthConfig에 null이 들어가게 되는데 Ktor는 인증 문서에 따르면 내부적으로 토큰을 로드하는데 캐싱된 토큰을 가져오기 때문에 로그인 이후에 갱신된 토큰에 접근하지 못하고 캐싱된 null에 접근하는 이슈를 확인했습니다.그래서 저희처럼 만료 주기가 짧은 경우 캐시를 제어할 수 있도록 3.4.0 버전에서 업데이트가 되었지만 Ktor 버전을 3.4.0으로 올리려 했지만 kotlin, hilt, agp, serialization 등 관련된 다른 버전도 모두 올려주어야 해서 지금 버전을 올리기에는 어렵다고 판단해 소셜로그인 후 캐시를 수동으로 제거하기 위해
AuthCacheManager를 정의했습니다.Q1. 프로필 이미지 변경 시에 서버에서 내려주는 이미지는 String(url)이고(기본 이미지도 url), 앨범에서 선택 시 Uri, 기본 이미지로 설정 시 null 총 3가지 타입에 대해서 상태를 유지해야 하는데 해당 방법 외에 조금 더 나은 아이디어가 있으실까요?
Q2. 프로필 이미지를 변경한 후 이전화면으로 돌아갈 떄 새로운 이미지 url을 로드하는 동안 잠시 아무것도 보이지 않는 현상이 있습니다. 그렇기 때문에 사용자 정보 조회 API를 통해 조회한 새로운 프로필 이미지 url을 캐싱해두고 이전 화면으로 돌아가려 합니다.
캐싱하는 과정에서 context가 필요한데 screen에서 캐싱 요청/캐싱 완료를 의미하는 Intent/Effect를 정의하여 사용자 정보 조회 API -> 캐싱 요청 Effect -> 캐싱 완료 intent -> 뒤로가기 Effect -> 뒤로가기. 의 순서로 진행을 할지, 혹은 Viewmodel에 Context를 주입하여
캐싱 완료 후 바로 뒤로가기 Effect를 발생시킬지 고민이 되었는데 현재 ViewModel에 context를 주입하는 방법으로 개발하였습니다. 어떤 방식이 나을지, 혹은 떠오르는 더 좋은 방법이 있으실까요?
캐싱 후 프로필 이미지 url을 로드하는 현상이 제거된 녹화영상입니다.
KakaoTalk_Video_2026-02-05-01-04-00.mp4
Summary by CodeRabbit
릴리스 노트
New Features
Improvements