Skip to content

[TWI-130] 초대 코드 공유 및 딥링크 진입 처리#131

Open
chanho0908 wants to merge 15 commits intodevelopfrom
feat/#130-invitation
Open

[TWI-130] 초대 코드 공유 및 딥링크 진입 처리#131
chanho0908 wants to merge 15 commits intodevelopfrom
feat/#130-invitation

Conversation

@chanho0908
Copy link
Member

@chanho0908 chanho0908 commented Mar 22, 2026

이슈 번호

closes #130

작업내용

요구사항

1. 멘트

[키피럽 함께 시작해요]
함께 시작하고 일상  시너지를!

1. '키피럽' 설치해 주세요. [스토어 링크]
2. 회원가입을  주세요.
3. 아래 링크를 통해 연결하거나, 연결 코드를 메이트과 공유하세요!

[딥링크]

2. 액션

상태 동작
앱 미설치 웹 페이지 → Play Store 리다이렉트
미로그인 로그인 완료 후 초대 코드 자동 입력된 상태로 초대코드 입력 화면 이동
로그인 + 커플 연결 전 초대 코드 자동 입력된 상태로 초대코드 입력 화면 이동
로그인 + 커플 연결 완료 무시

배포는 우리 파이어베이스 계정에 Firebase Hosting 사용해서 했어 !!
image

초대 링크 공유하기

  • CoupleConnectScreen의 공유하기 버튼 클릭 시 Android 기본 공유 시트 호출
  • 공유 텍스트: 앱 소개 + Play Store URL + 초대 딥링크 포함

딥링크 처리 (App Links)

  • Firebase Hosting(keepiluv.web.app) 배포: 앱 미설치 시 Play Store 리다이렉트
  • AndroidManifest: twix:// custom scheme + https://keepiluv.web.app App Links intent-filter 등록
  • InviteLaunchDispatcher: custom scheme / App Links 두 가지 scheme 모두 처리

결과물

공유하기 딥링크 진입 (회원가입 X) 딥링크 진입 (회원가입 O)
공유 시트 호출 초대 코드 자동 입력 Play Store 이동
Image Image Image

리뷰어에게 추가로 요구하는 사항 (선택)

  • 이전에 이야기했던 스플래시 화면에서 온보딩이 완료되지 않았을 때 토큰 여부에 따라 메인 화면으로 이동하던 문제 수정하는거
    이번 작업하면서 필요해서 내가 구현해놨어 ! 현수가 구현한거랑 다르거나 추가로 설정해야하는 작업 있으면 말해줘 :)
  • 처음 구현해보는 기능이라 어떤 엣지케이스가 엣지 케이스가 있을지 잘 예측이 안 돼서 현수가 아는 케이스가 있다면 체크 부탁해 :)
  • 릴리즈 빌드에서 잘 돌아가는지 테스트 부탁해 !!

chanho0908 and others added 7 commits March 22, 2026 21:20
- 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 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ShareInviteLink Intent/SideEffect 추가
- OnBoardingViewModel: ShareInviteLink 인텐트 처리
- CoupleConnectRoute: 공유하기 버튼 클릭 시 딥링크 + 스토어 URL 포함한 텍스트 공유
- strings.xml: 공유 메시지 문자열 상수화

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- NavRoutes.InviteRoute: code 쿼리 파라미터 추가 및 createRoute() 함수 구현
- OnboardingNavGraph: InviteRoute navArgument 등록, initialInviteCode 전달
- InviteCodeRoute: initialInviteCode로 코드 자동 입력 처리
- CoupleConnectionRoute → InviteRoute 이동 시 createRoute() 사용으로 {code} 리터럴 버그 수정

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- SplashViewModel: 토큰 갱신 성공 후 온보딩 상태 체크 추가
- SplashSideEffect: NavigateToOnBoarding(status) 추가
- SplashRoute: navigateToOnBoarding 콜백 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MainActivity: InviteLaunchEventSource inject 및 onCreate/onNewIntent에서 dispatchFromIntent 호출
- AppNavHost: inviteLaunchEventSource koinInject 기본값 적용
- SplashNavGraph: 온보딩 미완료 + pendingInviteCode 존재 시 InviteRoute로 직접 이동
- LoginNavGraph: 로그인 완료 후 COUPLE_CONNECTION 상태 + pendingInviteCode 존재 시 InviteRoute로 이동

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AndroidManifest: singleTask launchMode, custom scheme(twix://) 및 App Links(https://keepiluv.web.app) intent-filter 추가
- Firebase Hosting: 앱 미설치 시 Play Store 리다이렉트 페이지 배포
- .gitignore: assetlinks.json 민감 정보 제외

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chanho0908 chanho0908 linked an issue Mar 22, 2026 that may be closed by this pull request
2 tasks
@chanho0908 chanho0908 changed the title ✨ Feat: 초대 코드 공유 및 딥링크 진입 처리 초대 코드 공유 및 딥링크 진입 처리 Mar 22, 2026
@chanho0908 chanho0908 self-assigned this Mar 22, 2026
@chanho0908 chanho0908 added the Feature Extra attention is needed label Mar 22, 2026
coderabbitai[bot]

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt (1)

43-64: pendingInviteCode.value 직접 접근 방식에 대한 검토

PR 설명에서 요청하신 대로 StateFlow의 .value 직접 접근 방식을 검토했습니다.

현재 구현 방식:

  • Line 47에서 inviteLaunchEventSource.pendingInviteCode.value를 동기적으로 읽고 있습니다.
  • 이 접근법은 네비게이션 콜백이 호출되는 시점의 스냅샷 값만 읽습니다.

이 방식은 다음과 같은 상황에서 유효합니다:

  1. Splash → OnBoarding 전환은 일회성 이벤트이므로 StateFlow를 계속 관찰할 필요가 없음
  2. 딥링크 코드는 앱 시작 시 이미 설정되어 있어야 함

그러나 OnboardingNavGraph.kt에서는 collectAsStateWithLifecycle() + LaunchedEffect 패턴을 사용하고 있어 두 파일 간 일관성이 다릅니다.

제안: 현재 방식이 의도적이라면 주석으로 이유를 명시하면 향후 유지보수에 도움이 될 것 같습니다. 예를 들어:

// 스플래시 전환은 일회성이므로 StateFlow 스냅샷 값을 직접 읽음
val pendingCode = inviteLaunchEventSource.pendingInviteCode.value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt`
around lines 43 - 64, The code reads
inviteLaunchEventSource.pendingInviteCode.value directly inside the
navigateToOnBoarding lambda in SplashNavGraph (synchronous snapshot access)
which differs from other files that use collectAsStateWithLifecycle(); if this
direct .value access is intentional because the splash-to-onboarding transition
is a one-off and deep link is pre-populated, add a concise comment next to the
read (referencing inviteLaunchEventSource.pendingInviteCode.value and the
navigateToOnBoarding lambda in SplashNavGraph) explaining that the synchronous
snapshot is deliberate and why, so future maintainers understand the reasoning.
feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt (1)

104-106: DdayRoute의 navigateToBack 동작 확인 필요

navigateToBack에서 popBackStack() 대신 navController.navigate(NavRoutes.ProfileRoute.route)를 사용하고 있습니다.

이 방식은 백스택에 ProfileRoute를 새로 추가하므로, 사용자가 뒤로가기를 여러 번 누르면 중복된 화면이 나타날 수 있습니다. 의도적인 구현인지 확인이 필요합니다.

만약 단순히 이전 화면으로 돌아가는 것이 목적이라면:

♻️ popBackStack 사용 제안
                    navigateToBack = {
-                       navController.navigate(NavRoutes.ProfileRoute.route)
+                       navController.popBackStack()
                    },
🤖 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/navigation/OnboardingNavGraph.kt`
around lines 104 - 106, 현재 DdayRoute의 콜백 navigateToBack이
navController.navigate(NavRoutes.ProfileRoute.route)를 호출해 ProfileRoute를 새로 푸시하고
있어 백스택 중복을 일으킬 수 있습니다; 의도한 동작이 아니라면 navigateToBack에서
navController.popBackStack()을 호출하도록 변경해 이전 화면으로 돌아가게 하거나 특정 목적지로 되돌리고 싶다면
navController.popBackStack(NavRoutes.ProfileRoute.route, false)처럼 popBackStack을
사용해 중복된 인스턴스 생성을 방지하세요; 관련 식별자: navigateToBack, navController.navigate,
NavRoutes.ProfileRoute.route, navController.popBackStack().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt`:
- Line 57: The SplashRoute branch for OnboardingStatus.COMPLETED currently does
a bare return (return@SplashRoute) which can leave the user stuck on the splash
screen; update the handler to call navigateToMain() instead of returning so the
app navigates to the main screen (this aligns with
SplashViewModel.checkOnboardingStatus() emitting NavigateToMain), and apply the
same change pattern to the analogous branch in LoginNavGraph or, if you truly
consider COMPLETED unreachable, replace the return with an explicit error()
throw and a clear comment explaining why.
- Around line 60-64: The current flow in SplashNavGraph.kt calls
navController.navigate(NavRoutes.OnboardingGraph.route) then immediately
navController.navigate(destination), which leaves an unwanted intermediate
CoupleConnectionRoute on the back stack; change the logic to perform a single
direct navigation to the target destination (use
navController.navigate(destination) with the
popUpTo(NavRoutes.SplashGraph.route) { inclusive = true } and launchSingleTop =
true options) instead of first navigating to NavRoutes.OnboardingGraph.route, so
the back stack goes straight from SplashGraph to the requested onboarding child
(destination) without the extra startDestination entry.

---

Nitpick comments:
In
`@feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt`:
- Around line 104-106: 현재 DdayRoute의 콜백 navigateToBack이
navController.navigate(NavRoutes.ProfileRoute.route)를 호출해 ProfileRoute를 새로 푸시하고
있어 백스택 중복을 일으킬 수 있습니다; 의도한 동작이 아니라면 navigateToBack에서
navController.popBackStack()을 호출하도록 변경해 이전 화면으로 돌아가게 하거나 특정 목적지로 되돌리고 싶다면
navController.popBackStack(NavRoutes.ProfileRoute.route, false)처럼 popBackStack을
사용해 중복된 인스턴스 생성을 방지하세요; 관련 식별자: navigateToBack, navController.navigate,
NavRoutes.ProfileRoute.route, navController.popBackStack().

In `@feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt`:
- Around line 43-64: The code reads
inviteLaunchEventSource.pendingInviteCode.value directly inside the
navigateToOnBoarding lambda in SplashNavGraph (synchronous snapshot access)
which differs from other files that use collectAsStateWithLifecycle(); if this
direct .value access is intentional because the splash-to-onboarding transition
is a one-off and deep link is pre-populated, add a concise comment next to the
read (referencing inviteLaunchEventSource.pendingInviteCode.value and the
navigateToOnBoarding lambda in SplashNavGraph) explaining that the synchronous
snapshot is deliberate and why, so future maintainers understand the reasoning.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 2c3efdbc-485c-4403-93a6-55fc53c12b11

📥 Commits

Reviewing files that changed from the base of the PR and between a257a9b and 01eb94f.

📒 Files selected for processing (8)
  • core/design-system/src/main/res/values/strings.xml
  • core/navigation-contract/src/main/java/com/twix/navigation_contract/InviteLaunchEventSource.kt
  • core/navigation/src/main/java/com/twix/navigation/AppNavHost.kt
  • core/share/src/main/java/com/twix/share/InviteLaunchDispatcher.kt
  • feature/onboarding/src/main/java/com/twix/onboarding/couple/CoupleConnectRoute.kt
  • feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt
  • feature/splash/src/main/java/com/twix/splash/contract/SplashSideEffect.kt
  • feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt
✅ Files skipped from review due to trivial changes (2)
  • core/design-system/src/main/res/values/strings.xml
  • core/navigation-contract/src/main/java/com/twix/navigation_contract/InviteLaunchEventSource.kt
🚧 Files skipped from review as they are similar to previous changes (3)
  • feature/splash/src/main/java/com/twix/splash/contract/SplashSideEffect.kt
  • core/navigation/src/main/java/com/twix/navigation/AppNavHost.kt
  • core/share/src/main/java/com/twix/share/InviteLaunchDispatcher.kt

- `onboarding_invite_share_message` 문자열의 가독성 향상을 위해 타이틀 및 연결 코드 부분에 줄바꿈(`\n`) 반영
@chanho0908 chanho0908 changed the title 초대 코드 공유 및 딥링크 진입 처리 [TWI-130]초대 코드 공유 및 딥링크 진입 처리 Mar 22, 2026
@chanho0908 chanho0908 changed the title [TWI-130]초대 코드 공유 및 딥링크 진입 처리 [TWI-130] 초대 코드 공유 및 딥링크 진입 처리 Mar 22, 2026
chanho0908 and others added 2 commits March 22, 2026 22:40
불필요한 중간 화면이 백스택에 쌓이는 문제 수정
OnboardingGraph로 먼저 이동 후 destination으로 이동하는 2단계 패턴을
destination으로 직접 이동하는 단일 호출로 변경

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
서버 응답 G4000(400) 코드에 대한 처리 추가
- strings.xml: toast_self_invite_code 문자열 추가
- OnBoardingViewModel: SELF_INVITE_CODE_ERROR_CODE 상수 추가 및 handleCoupleConnectException에 G4000 케이스 처리
- 기존 else 미처리 케이스도 일반 에러 토스트로 정리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 60b79f8 and 88d4055.

📒 Files selected for processing (3)
  • core/design-system/src/main/res/values/strings.xml
  • feature/onboarding/src/main/java/com/twix/onboarding/OnBoardingViewModel.kt
  • feature/splash/src/main/java/com/twix/splash/navigation/SplashNavGraph.kt

InviteRoute에서도 pendingInviteCode를 collectAsStateWithLifecycle로 구독하여
딥링크 수신 시 WriteInviteCode intent로 코드 자동 입력 처리

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@YAPP-Github YAPP-Github deleted a comment from coderabbitai bot Mar 22, 2026
@YAPP-Github YAPP-Github deleted a comment from coderabbitai bot Mar 22, 2026
@chanho0908 chanho0908 requested a review from dogmania March 23, 2026 03:50
@chanho0908 chanho0908 removed the request for review from dogmania March 23, 2026 03:51
@coderabbitai
Copy link

coderabbitai bot commented Mar 23, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

이 변경은 초대 코드를 통한 앱 공유 및 딥링크 처리 기능을 구현합니다. Firebase 호스팅 설정을 추가하고, 새로운 core/share 모듈에서 InviteLaunchDispatcher로 커스텀 스킴(twix://invite)과 웹 링크(https://keepiluv.web.app)의 초대 딥링크를 처리합니다. 로그인, 온보딩, 스플래시 화면의 네비게이션 계층에서 InviteLaunchEventSource를 주입받아 pending invite code를 감시하고 해당 화면으로 라우팅합니다. 온보딩의 커플 연결 화면에서 초대 링크를 공유할 수 있는 UI 플로우도 추가됩니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55분


상세 검토 의견

✅ 우수한 점

1. 명확한 아키텍처 계층화

  • InviteLaunchEventSource 인터페이스를 core/navigation-contract에 정의하고, 구현체 InviteLaunchDispatchercore/share에 분리한 설계가 깔끔합니다.
  • DI 모듈 시스템을 통해 의존성이 느슨하게 유지되어 테스트성이 좋습니다.

2. 일관된 패턴 적용

  • LoginNavGraph, OnboardingNavGraph, SplashNavGraph에서 동일한 방식으로 pendingInviteCode를 감시하고 소비하는 패턴이 반복되어 코드 일관성이 우수합니다.

3. 안전한 상태 관리

  • StateFlow<String?>를 통해 nullable invite code를 안전하게 관리하고, consumePendingInviteCode()로 명시적으로 상태를 정리하는 방식이 좋습니다.

⚠️ 검토가 필요한 부분

1. 네비게이션 레이어의 LaunchedEffect 중복

여러 네비게이션 그래프에서 비슷한 LaunchedEffect 패턴이 반복됩니다:

// LoginNavGraph, OnboardingNavGraph, SplashNavGraph에서 유사하게 반복
LaunchedEffect(eventSource.pendingInviteCode) {
    eventSource.pendingInviteCode.collect { code ->
        if (code != null) {
            eventSource.consumePendingInviteCode()
            navigate(NavRoutes.InviteRoute.createRoute(code))
        }
    }
}

개선 제안:

  • 이 로직을 InviteLaunchEventSource의 확장 함수로 추출하거나, 공통 네비게이션 헬퍼 함수로 분리하면 어떨까요?
    // core/navigation에 추가
    fun InviteLaunchEventSource.observeAndNavigate(
        onNavigateToInvite: (String) -> Unit
    ) {
        LaunchedEffect(pendingInviteCode) {
            pendingInviteCode.collect { code ->
                if (code != null) {
                    consumePendingInviteCode()
                    onNavigateToInvite(code)
                }
            }
        }
    }

2. InviteLaunchDispatcher의 URI 검증 로직

dispatchFromIntent(intent: Intent?) 메서드에서 URI scheme/host 검증이 명확하지 않습니다.

검토 질문:

  • HTTP/HTTPS 스킴 검증 시, 앱 링크 자동 검증(android:autoVerify="true")과의 관계는 어떻게 되나요?
  • 악의적인 링크(예: twix://invite?code=../../sensitive)로부터의 보호는 충분한가요?

개선 제안:

  • URI 검증 로직을 더 명확한 이름의 private 함수로 분리하고, 유효성 검사 순서를 문서화하면 좋을 것 같습니다.

3. 에러 처리: OnBoardingViewModel.handleCoupleConnectException

// 변경 후 코드
when {
    error is AppError.Http && error.status == 400 && 
    error.code == SELF_INVITE_CODE_ERROR_CODE -> { ... }
    error is AppError.Http && error.status == 404 -> { ... }
    else -> { /* 기타 에러 */ }
}

검토 질문:

  • 404 체크가 이전과 동일하게 동작하는지 확인되었나요? (error.code 체크 없이 status만 확인)
  • 400 에러가 다른 원인일 수도 있는데, SELF_INVITE_CODE_ERROR_CODE 검증이 충분히 안전한가요?

개선 제안:

  • 에러 코드 검증 순서를 명확하게 하고, 각 case별로 로깅을 추가하면 실제 운영 시 디버깅이 수월할 것 같습니다.

4. NavRoutes.InviteRoute.createRoute(code: String?) 유틸리티

fun createRoute(code: String? = null): String {
    return if (code != null) "invite?code=$code" else "invite?code="
}

검토 질문:

  • null일 때 "invite?code="로 빈 값을 전달하는 이유가 무엇인가요? 그냥 "invite"로 반환하면 안 되나요?
  • URL 인코딩이 필요한 경우(특수 문자 포함) 어떻게 처리할 계획인가요?

5. Firebase 웹 호스팅 리다이렉트

public/index.html에서:

let storeUrl = "https://play.google.com/store/apps/details?id=com.yapp.twix";
location.replace(storeUrl);

검토 질문:

  • 로그인한 사용자가 웹 링크를 클릭했을 때, 초대 코드 파라미터(?code=xxx)가 로드되는 동안 스크립트 실행이 완료될 수 있나요?
  • 웹 링크(keepiluv.web.app)의 실제 웹 애플리케이션이 있다면, 리다이렉트 전에 초대 코드를 처리해야 하지 않을까요?

개선 제안:

  • 만약 웹앱이 향후 실제 서비스를 제공한다면, 서버에서 초대 코드를 검증하고 앱 링크로 변환하는 로직을 고려해보세요.

📝 링크된 이슈 검증

이슈 #130: "초대장 보내기 기능 개발"의 TODO 항목:

  • 앱 설치 시 초대코드 입력 화면으로 이동

    • InviteLaunchDispatchertwix://invite?code=xxxhttps://keepiluv.web.app?code=xxx 처리
    • 각 네비게이션 계층에서 pending code를 감시하여 InviteRoute로 라우팅
  • 앱 미설치 시 플레이스토어로 이동

    • public/index.html의 리다이렉트 로직이 구현됨
    • Firebase 호스팅 설정으로 웹 링크 서빙 준비 완료

최종 제안

이 PR은 아키텍처와 실행 방식이 전반적으로 좋습니다. 위의 검토 항목들은 주로 엣지 케이스 처리와 코드 응집도 측면에서 고려할 사항이며, 반드시 해결해야 하는 critical issue는 아닙니다. 다만:

  1. 네비게이션 LaunchedEffect 중복 제거 → 유지보수성 향상
  2. 에러 처리 로직 재검토 → 실제 상황에서의 동작 확인
  3. Firebase 웹 리다이렉트 로직 → 웹앱 실현 시 재검토

이 부분들이 차후 리팩토링 또는 테스트 진행 시 함께 고려되면 좋을 것 같습니다. 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 초대 코드 공유 및 딥링크 진입 처리라는 주요 변경사항을 명확하게 요약하고 있으며, 변경 내용과 직접 관련성이 높습니다.
Linked Issues check ✅ Passed PR이 #130 이슈의 모든 요구사항을 충족합니다: (1) 앱 설치 시 초대코드 입력 화면으로 이동 [구현], (2) 앱 미설치 시 플레이스토어로 리다이렉트 [Firebase Hosting 배포].
Out of Scope Changes check ✅ Passed 모든 변경사항이 초대 코드 공유 및 딥링크 진입 처리라는 명확한 범위 내에 있으며, 불필요한 추가 기능이나 범위 외 수정은 없습니다.
Description check ✅ Passed PR 설명이 이슈 #130과 관련이 있으며, 구현된 기능(초대 코드 공유, 딥링크 처리, Firebase Hosting 배포)을 명확히 설명하고 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#130-invitation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chanho0908 chanho0908 requested a review from dogmania March 23, 2026 03:54
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt (1)

69-86: NavGraph에서 코드 주입 경로가 이중화되어 상태 흐름이 복잡해집니다

왜 문제인지: 현재는 initialInviteCode 전달(Line 85)과 vm.dispatch(OnBoardingIntent.WriteInviteCode(...))(Line 76)를 모두 사용합니다. 같은 상태를 두 경로에서 세팅하면 추후 유지보수 시 동기화 누락이 생기기 쉽습니다.
어떻게 개선할지: 코드 주입 경로를 하나로 통일하는 게 좋습니다(예: InviteCodeRoute/ViewModel에서 단일 진입점으로 처리). 이 방향으로 정리하면 어떨까요?

As per coding guidelines feature/**: "단방향 데이터 플로우(Intent → ViewModel → State → View)가 유지되는가?"

🤖 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/navigation/OnboardingNavGraph.kt`
around lines 69 - 86, The nav graph is setting the invite code in two places
which duplicates state flow; choose one entrypoint—either have
InviteCodeRoute/its ViewModel handle the code (keep initialInviteCode on
InviteCodeRoute and remove the LaunchedEffect +
inviteLaunchEventSource.pendingInviteCode collection and
vm.dispatch(OnBoardingIntent.WriteInviteCode(...))), or keep the LaunchedEffect
dispatch path and remove passing initialInviteCode into InviteCodeRoute; update
code so only one of InviteCodeRoute.initialInviteCode or
vm.dispatch(OnBoardingIntent.WriteInviteCode) (and
inviteLaunchEventSource.pendingInviteCode usage) is used to seed the ViewModel
state (referencing InviteCodeRoute, initialInviteCode,
inviteLaunchEventSource.pendingInviteCode.collectAsStateWithLifecycle, and
OnBoardingIntent.WriteInviteCode).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt`:
- Around line 43-47: The code consumes the pending invite code before navigation
and calls InviteRoute.createRoute(code) without percent-encoding, risking lost
codes on navigation failure and broken routes for special characters; change the
flow to first build the encoded route using android.net.Uri.encode(code) (or
have InviteRoute.createRoute perform encoding), call navController.navigate(...)
with that encoded route, and only after successful navigation call
inviteLaunchEventSource.consumePendingInviteCode(); update usages in
LaunchedEffect where pendingInviteCode is read and in InviteRoute.createRoute to
apply Uri.encode to the code.

---

Nitpick comments:
In
`@feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt`:
- Around line 69-86: The nav graph is setting the invite code in two places
which duplicates state flow; choose one entrypoint—either have
InviteCodeRoute/its ViewModel handle the code (keep initialInviteCode on
InviteCodeRoute and remove the LaunchedEffect +
inviteLaunchEventSource.pendingInviteCode collection and
vm.dispatch(OnBoardingIntent.WriteInviteCode(...))), or keep the LaunchedEffect
dispatch path and remove passing initialInviteCode into InviteCodeRoute; update
code so only one of InviteCodeRoute.initialInviteCode or
vm.dispatch(OnBoardingIntent.WriteInviteCode) (and
inviteLaunchEventSource.pendingInviteCode usage) is used to seed the ViewModel
state (referencing InviteCodeRoute, initialInviteCode,
inviteLaunchEventSource.pendingInviteCode.collectAsStateWithLifecycle, and
OnBoardingIntent.WriteInviteCode).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: ee6c43e8-5add-4438-badc-6ee6b01ab371

📥 Commits

Reviewing files that changed from the base of the PR and between 88d4055 and 12e3dd0.

📒 Files selected for processing (2)
  • core/design-system/src/main/res/values/strings.xml
  • feature/onboarding/src/main/java/com/twix/onboarding/navigation/OnboardingNavGraph.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/design-system/src/main/res/values/strings.xml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Extra attention is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

초대장 보내기 기능 개발

1 participant