Conversation
- InviteLaunchDispatcher: custom scheme(twix://) 및 App Links(https) 처리 - InviteLaunchEventSource: INVITE_WEB_HOST, PLAY_STORE_URL 상수 및 buildInviteDeepLink 추가 - core:share 모듈을 settings.gradle.kts, app/build.gradle.kts에 등록 - Koin shareModule 등록
- ShareInviteLink Intent/SideEffect 추가 - OnBoardingViewModel: ShareInviteLink 인텐트 처리 - CoupleConnectRoute: 공유하기 버튼 클릭 시 딥링크 + 스토어 URL 포함한 텍스트 공유 - strings.xml: 공유 메시지 문자열 상수화
- NavRoutes.InviteRoute: code 쿼리 파라미터 추가 및 createRoute() 함수 구현
- OnboardingNavGraph: InviteRoute navArgument 등록, initialInviteCode 전달
- InviteCodeRoute: initialInviteCode로 코드 자동 입력 처리
- CoupleConnectionRoute → InviteRoute 이동 시 createRoute() 사용으로 {code} 리터럴 버그 수정
- SplashViewModel: 토큰 갱신 성공 후 온보딩 상태 체크 추가 - SplashSideEffect: NavigateToOnBoarding(status) 추가 - SplashRoute: navigateToOnBoarding 콜백 추가
- MainActivity: InviteLaunchEventSource inject 및 onCreate/onNewIntent에서 dispatchFromIntent 호출 - AppNavHost: inviteLaunchEventSource koinInject 기본값 적용 - SplashNavGraph: 온보딩 미완료 + pendingInviteCode 존재 시 InviteRoute로 직접 이동 - LoginNavGraph: 로그인 완료 후 COUPLE_CONNECTION 상태 + pendingInviteCode 존재 시 InviteRoute로 이동
- AndroidManifest: singleTask launchMode, custom scheme(twix://) 및 App Links(https://keepiluv.web.app) intent-filter 추가 - Firebase Hosting: 앱 미설치 시 Play Store 리다이렉트 페이지 배포 - .gitignore: assetlinks.json 민감 정보 제외
- `onboarding_invite_share_message` 문자열의 가독성 향상을 위해 타이틀 및 연결 코드 부분에 줄바꿈(`\n`) 반영
불필요한 중간 화면이 백스택에 쌓이는 문제 수정 OnboardingGraph로 먼저 이동 후 destination으로 이동하는 2단계 패턴을 destination으로 직접 이동하는 단일 호출로 변경
서버 응답 G4000(400) 코드에 대한 처리 추가 - strings.xml: toast_self_invite_code 문자열 추가 - OnBoardingViewModel: SELF_INVITE_CODE_ERROR_CODE 상수 추가 및 handleCoupleConnectException에 G4000 케이스 처리 - 기존 else 미처리 케이스도 일반 에러 토스트로 정리
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt (1)
52-53:⚠️ Potential issue | 🟠 Major초대 코드 로딩 전 공유 시 빈 코드가 전송될 수 있습니다.
초기 로딩이 끝나기 전에 공유 액션이 들어오면 빈 문자열이 그대로 공유되어 링크 품질이 깨집니다. 공유 전에
isBlank()를 검사하고, 비어 있으면 토스트 후 재조회하도록 가드해 주세요.가드 로직 제안
- OnBoardingIntent.ShareInviteLink -> - emitSideEffect(OnBoardingSideEffect.InviteCode.ShareInviteLink(currentState.inviteCode.myInviteCode)) + OnBoardingIntent.ShareInviteLink -> { + val inviteCode = currentState.inviteCode.myInviteCode + if (inviteCode.isBlank()) { + showToast(R.string.onboarding_couple_fetch_my_invite_code_fail, ToastType.ERROR) + fetchMyInviteCode() + return + } + emitSideEffect(OnBoardingSideEffect.InviteCode.ShareInviteLink(inviteCode)) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt` around lines 52 - 53, 현재 OnBoardingViewModel에서 OnBoardingIntent.ShareInviteLink 처리 시 currentState.inviteCode.myInviteCode를 바로 공유해 빈 문자열이 전송될 수 있으니, ShareInviteLink 분기에서 inviteCode = currentState.inviteCode.myInviteCode를 받아 isBlank()로 검사하고 비어있으면 emitSideEffect로 토스트(예: OnBoardingSideEffect.ShowToast("초대코드 로딩중입니다"))를 발행한 뒤 재조회용 인텐트(예: dispatch(OnBoardingIntent.LoadInviteCode) 또는 loadInviteCode() 호출)를 트리거해 중복 전송을 막고, 비어있지 않을 때만 emitSideEffect(OnBoardingSideEffect.InviteCode.ShareInviteLink(inviteCode))를 호출하도록 변경하세요.
🧹 Nitpick comments (1)
feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt (1)
106-117: 404 케이스의error.message문자열 분기는 취약합니다.메시지 문구가 서버/번역 정책으로 바뀌면 분기가 바로 깨질 수 있습니다. 404도 백엔드와 에러
code계약이 가능하다면 code 기반 분기로 전환하는 쪽이 더 안전한데, 이 방향으로 맞춰보는 건 어떨까요?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt` around lines 106 - 117, The 404 branch in the when block currently inspects error.message (lines handling AppError.Http && error.status == 404) which is fragile; change it to branch on a stable error code property (e.g., use AppError.Http.code or add one if missing) instead of INVALID_INVITE_CODE_MESSAGE / ALREADY_USED_INVITE_CODE_MESSAGE string constants, updating the conditional inside the AppError.Http && error.status == 404 case to check error.code values and then call showToast(...) or emitSideEffect(OnBoardingSideEffect.InviteCode.NavigateToNext) accordingly; keep a safe fallback to the existing message-based checks only when error.code is null to preserve behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@core/design-system/src/main/res/values/strings.xml`:
- Line 191: Fix the typo in the onboarding invite share message string resource:
update the value of the string named "onboarding_invite_share_message" to
replace "메이트과" with the correct particle "메이트와" so the displayed share text
reads correctly for users.
In `@feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt`:
- Around line 47-53: The current branch choosing logic uses only pendingCode !=
null which allows empty strings to be treated as valid; update the check around
inviteLaunchEventSource.pendingInviteCode.value so you treat blank/empty the
same as null (use isNullOrBlank()), call
inviteLaunchEventSource.consumePendingInviteCode() when a pending value exists,
and only call NavRoutes.InviteRoute.createRoute(pendingCode) when
pendingCode.isNullOrBlank() is false; otherwise fall back to
NavRoutes.CoupleConnectionRoute.route.
---
Duplicate comments:
In `@feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt`:
- Around line 52-53: 현재 OnBoardingViewModel에서 OnBoardingIntent.ShareInviteLink
처리 시 currentState.inviteCode.myInviteCode를 바로 공유해 빈 문자열이 전송될 수 있으니,
ShareInviteLink 분기에서 inviteCode = currentState.inviteCode.myInviteCode를 받아
isBlank()로 검사하고 비어있으면 emitSideEffect로 토스트(예:
OnBoardingSideEffect.ShowToast("초대코드 로딩중입니다"))를 발행한 뒤 재조회용 인텐트(예:
dispatch(OnBoardingIntent.LoadInviteCode) 또는 loadInviteCode() 호출)를 트리거해 중복 전송을
막고, 비어있지 않을 때만
emitSideEffect(OnBoardingSideEffect.InviteCode.ShareInviteLink(inviteCode))를
호출하도록 변경하세요.
---
Nitpick comments:
In `@feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt`:
- Around line 106-117: The 404 branch in the when block currently inspects
error.message (lines handling AppError.Http && error.status == 404) which is
fragile; change it to branch on a stable error code property (e.g., use
AppError.Http.code or add one if missing) instead of INVALID_INVITE_CODE_MESSAGE
/ ALREADY_USED_INVITE_CODE_MESSAGE string constants, updating the conditional
inside the AppError.Http && error.status == 404 case to check error.code values
and then call showToast(...) or
emitSideEffect(OnBoardingSideEffect.InviteCode.NavigateToNext) accordingly; keep
a safe fallback to the existing message-based checks only when error.code is
null to preserve behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: cf08e12c-44fe-4a7a-812d-a881f18bb6db
📒 Files selected for processing (3)
core/design-system/src/main/res/values/strings.xmlfeature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.ktfeature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt
feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt
Outdated
Show resolved
Hide resolved
InviteRoute에서도 pendingInviteCode를 collectAsStateWithLifecycle로 구독하여 딥링크 수신 시 WriteInviteCode intent로 코드 자동 입력 처리
|
@coderabbitai review |
dogmania
left a comment
There was a problem hiding this comment.
고생하셨습니다! 되게 오랜만에 코드 보니까 재밌네요
| private suspend fun checkOnboardingStatus() { | ||
| when (val result = onBoardingRepository.fetchOnboardingStatus()) { | ||
| is AppResult.Success -> | ||
| when (result.data) { | ||
| OnboardingStatus.COMPLETED -> tryEmitSideEffect(SplashSideEffect.NavigateToMain) | ||
| else -> tryEmitSideEffect(SplashSideEffect.NavigateToOnBoarding(result.data)) | ||
| } | ||
| else -> tryEmitSideEffect(SplashSideEffect.NavigateToMain) | ||
| } | ||
| } |
There was a problem hiding this comment.
suspend 떼고 launchResult 써도 될 거 같아요
There was a problem hiding this comment.
:feature:splash에서 의존성 추가한 것처럼 이렇게 수정해 주세요!
implementation(projects.core.navigationContract)
| companion object { | ||
| const val INVITE_SCHEME = "twix" | ||
| const val INVITE_HOST = "invite" | ||
| const val INVITE_CODE_PARAM = "code" | ||
| const val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.yapp.twix" | ||
| const val INVITE_WEB_HOST = "keepiluv.web.app" | ||
|
|
||
| fun buildInviteDeepLink(inviteCode: String) = "https://$INVITE_WEB_HOST?$INVITE_CODE_PARAM=$inviteCode" | ||
| } |
There was a problem hiding this comment.
이런 구현에 필요한 값들을 인터페이스에 넣는 것보다는 구현체 내부에 두거나 공통 상수 object에 넣어서 공유하도록 하는 건 어떨까요? 인터페이스가 딱 계약만 가지도록 하면 좋을 것 같아요.
지금 구현체 제외하면 외부에서 사용하는 값이 PLAY_STORE_URL뿐인 것으로 확인되는데, 나머지 값들은 구현체 내부, URL은 공통 상수로 빼도 될 것 같습니다. 인터페이스를 계약만 가지도록 바꿔보시죠!
There was a problem hiding this comment.
엇 그러네 😓
만약 이 상수들이 여러 구현체들에 공통되게 사용된다면 인터페이스에 계약으로 명시하는게 맞을 것 같은데
지금은 하나의 구현체에서만 사용되서 구현체에 둬도 좋을거 같아 👍
리뷰 반영 커밋 : cfd105f
There was a problem hiding this comment.
LoginNavGraph, SplashNavGraph, OnboardingNavGraph에 지금 딥링크 수신 여부를 가지고 네비게이션을 처리하는 로직이 추가되어 있는데, 이렇게 하면 책임이 분산되고 NavGraph가 외부 진입을 처리하는 것까지 담당해야 해서 수정하면 좋을 것 같아요.
제가 푸시알림 딥링크 처리 로직 구현한 것처럼 MainActivity에서 Intent를 수신하고 AppNavHost가 이를 감지해서 바로 원하는 곳으로 네비게이션하도록 만들 수 있습니다. 구조가 아예 동일한지는 모르겠지만 큰틀에서 봤을 때 이런 라우팅 처리 책임은 AppNavHost가 가지고 NavGraph에서는 딥링크 처리 로직 제거해 주세요!
There was a problem hiding this comment.
나도 NavGraph마다 딥링크 처리 로직을 넣어주는걸 해결하고 싶었는데 방법을 못찾았걸랑ㅠㅠ 그런 좋은 방법이 있었군 ! 👍
리뷰 반영 커밋 : de011ec
feature/login/build.gradle.kts
Outdated
| } | ||
| } | ||
| dependencies { | ||
| implementation(project(":core:navigation-contract")) |
There was a problem hiding this comment.
implementation(projects.core.navigationContract) 이렇게 수정해 주세요!
| import kotlinx.coroutines.flow.asStateFlow | ||
|
|
||
| class InviteLaunchDispatcher : InviteLaunchEventSource { | ||
| private val _pendingInviteCode = MutableStateFlow<String?>(null) |
There was a problem hiding this comment.
저는 보통 이런 전역 이벤트를 관리할 때는 SharedFlow를 사용하는 편인데 여기에서 StateFlow를 사용한 이유가 있을까요?
MutableSharedFlow<String>(
replay = 0,
extraBufferCapacity = 1
)
이렇게 두면 중복 이벤트 처리도 가능하고 nullable도 없앨 수 있을 것 같아요
There was a problem hiding this comment.
나도 StateFlow는 상태를, SharedFlow와 Channel은 이벤트를 처리하는데 사용된다고 생각해
우선 여기서 StateFlow를 사용한 이유는 현수가 제안한 것 처럼 처음에SharedFlow에 extraBufferCapacity만 1로 줘서 사용했어
하지만 이렇게 하니까 UI가 완전히 그려지고 구독자가 붙기 전에 전에 값이 유실되는 문제가 발생했어
그래서 replay를 1로 줘서 소비되기 전까지 캐싱하려고 했는데
replay랑 extraBufferCapacity를 사용하는 법이 좀 햇갈려서 내가 잘못 이해하고 사용했다면 말해줘
이렇게 하면 SharedFlow를 사용해도 될거 같긴한데, 한가지 제안을 해보자면 SharedFlow는 이벤트 버스처럼
여러명의 구독자에게 브로드캐스트 하는 경우엔 적합하지만 위에 리뷰에서 현수가 제안해준 것 처럼
딥링크 수신 여부를 AppNavHost에서만 관리하도록 한다면 Channel도 좋은 선택지가 될 수 있을 것 같아
그리고 StateFlow를 사용한 이유는 SharedFlow에 캐싱을 하나 줘서 사용해도 되지만
현수가 NotificationLaunchDispatcher에 나와 비슷한 이유로 StateFlow를 사용한거 같아서
통일하고자 StateFlow를 사용했어 !!
- companion object 제거, 구현 관련 상수는 InviteLaunchDispatcher 내부로 이동 - PLAY_STORE_URL은 Constants 공통 상수 object로 분리 - feature/onboarding에 core:share 의존성 추가
12e3dd0 to
cfd105f
Compare
a092191 to
de011ec
Compare
이슈 번호
closes #130
작업내용
요구사항
1. 멘트
2. 액션
배포는 우리 파이어베이스 계정에 Firebase Hosting 사용해서 했어 !!

초대 링크 공유하기
CoupleConnectScreen의 공유하기 버튼 클릭 시 Android 기본 공유 시트 호출딥링크 처리 (App Links)
keepiluv.web.app) 배포: 앱 미설치 시 Play Store 리다이렉트AndroidManifest:twix://custom scheme +https://keepiluv.web.appApp Links intent-filter 등록InviteLaunchDispatcher: custom scheme / App Links 두 가지 scheme 모두 처리결과물
리뷰어에게 추가로 요구하는 사항 (선택)
이번 작업하면서 필요해서 내가 구현해놨어 ! 현수가 구현한거랑 다르거나 추가로 설정해야하는 작업 있으면 말해줘 :)
릴리즈 빌드에서 잘 돌아가는지 테스트 부탁해 !!