-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT/#386] 믹스패널 붙였어요 #407
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
Conversation
Walkthrough앱에 Mixpanel SDK를 통합하고 BuildConfig/의존성/CI 설정을 추가했으며, Mixpanel 래퍼와 여러 도메인별 이벤트 클래스 및 CompositionLocal 주입을 도입하고 주요 화면들에 이벤트 호출을 삽입했다. 스플래시 흐름과 일부 모델/매퍼/열거형도 확장됐다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as 사용자
participant App as MainActivity/Compose
participant VM as SplashViewModel
participant Repo as Token/User/SpoonRepo
participant MP as MixPanelEvents
participant SDK as Mixpanel SDK
U->>App: 앱 시작
App->>VM: 초기화 요청
VM->>Repo: 토큰 조회
alt 토큰 존재
VM->>Repo: 사용자 정보 요청
Repo-->>VM: UserModel
VM-->>App: SideEffect.NavigateToMap
App->>MP: analyticsEvents.appOpen()
App->>MP: userProperties.setUserProfile(userId, properties)
MP->>SDK: identify / people.set
else 토큰 없음
VM-->>App: SideEffect.NavigateToSignIn
App->>MP: analyticsEvents.appOpen()
end
sequenceDiagram
autonumber
participant Act as MainActivity
participant Loc as LocalTracker (CompositionLocal)
participant UI as Composable Screen
participant Evt as MixPanelEvents
participant Trk as MixPanelTracker
participant SDK as Mixpanel SDK
Act->>Loc: CompositionLocalProvider(LocalTracker = Evt)
UI->>Loc: LocalTracker.current
UI->>Evt: commonEvents.tabEntered("explore")
Evt->>Trk: track("tab_entered", props)
Trk->>SDK: track(event, props)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60–90 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
🧹 Nitpick comments (13)
app/src/main/java/com/spoony/spoony/presentation/follow/component/UserListScreen.kt (1)
25-25: 분석 이벤트 통합이 올바르게 구현되었습니다.
user.isFollowing상태를 기반으로 팔로우/언팔로우 이벤트를 로깅하는 로직이 정확합니다.user는 불변 리스트의 스냅샷이므로 버튼 클릭 이전의 상태를 올바르게 반영합니다.선택적 리팩토링: 코드 중복 제거
FOLLOWER와FOLLOWING분기의 로직이entryPoint문자열을 제외하고 동일합니다. 다음과 같이 리팩토링하여 중복을 줄일 수 있습니다:onFollowClick = { onButtonClick(user.userId) - - when (type) { - FollowType.FOLLOWER -> { - if (user.isFollowing) { - tracker.commonEvents.unfollowUser( - unfollowedUserId = user.userId, - entryPoint = "followed_list" - ) - } else { - tracker.commonEvents.followUser( - followedUserId = user.userId, - entryPoint = "followed_list" - ) - } - } - - FollowType.FOLLOWING -> { - if (user.isFollowing) { - tracker.commonEvents.unfollowUser( - unfollowedUserId = user.userId, - entryPoint = "following_list" - ) - } else { - tracker.commonEvents.followUser( - followedUserId = user.userId, - entryPoint = "following_list" - ) - } - } + + val entryPoint = when (type) { + FollowType.FOLLOWER -> "followed_list" + FollowType.FOLLOWING -> "following_list" + } + + if (user.isFollowing) { + tracker.commonEvents.unfollowUser( + unfollowedUserId = user.userId, + entryPoint = entryPoint + ) + } else { + tracker.commonEvents.followUser( + followedUserId = user.userId, + entryPoint = entryPoint + ) } },Also applies to: 43-75
app/src/main/java/com/spoony/spoony/domain/entity/LocationEntity.kt (1)
11-11: LocationEntity 인스턴스화 검토 완료, 기본값 지정 권장
검색 결과 모든 LocationEntity 생성이 named args로 이뤄져 positional 인자 이슈는 없음을 확인했습니다. 신규 필드인locationType에 기본값(= ""등)을 지정하면 향후 호환성 유지에 도움이 됩니다.app/src/main/java/com/spoony/spoony/presentation/gourmet/map/model/LocationModel.kt (1)
9-9: LocationModel.locationType nullable 여부 재검토
MapSearchScreen.kt:174에서locationInfo.locationType.orEmpty()로 null을 처리하고 있으나,LocationEntity.locationType은 non-null이므로LocationModel.locationType을 non-nullString(기본값"")으로 변경해 .orEmpty 호출을 제거하는 방안을 고려하세요.gradle/libs.versions.toml (1)
73-74: Mixpanel 버전을 고정해 주세요.
7.+와일드카드 버전은 빌드 시점마다 다른 아티팩트를 가져올 수 있어 재현성과 디버깅이 크게 떨어집니다. 안정적인 빌드를 위해 프로젝트에서 검증한 정확한 버전을 명시적으로 지정해 주세요.Also applies to: 153-155
app/src/main/java/com/spoony/spoony/presentation/explore/type/SortingOption.kt (1)
5-9: trackingCode 도입 적절합니다. 스펙 합의만 확인 부탁드립니다.
- 영문 고정값으로 i18n 영향 분리된 점 좋습니다.
- analytics 네이밍 스펙과 값(latest/most_saved) 합의 여부만 확인해 주세요. 또한 stringCode와 역할 구분을 KDoc 한 줄로 남겨두면 혼선이 줄어듭니다.
app/src/main/java/com/spoony/spoony/presentation/gourmet/search/MapSearchScreen.kt (2)
80-80: LocalTracker 제공 보장 확인CompositionLocal 미제공 시 런타임/프리뷰 크래시 위험 있습니다. 앱 루트(예: MainActivity/Theme)에서 항상 Provider가 깔리는지와 Preview에 대체 Provider가 있는지 확인해 주세요.
173-176: 빈 locationType 처리와 검색어 로깅 정책 확인 제안
- locationType이 비어있을 때 "" 대신 null/제외 또는 상수 "unknown"으로 정규화하면 분석 품질이 좋아집니다. 스펙상 가능하다면 아래처럼 대체를 권장합니다.
- tracker.mapEvents.mapSearched( - locationType = locationInfo.locationType.orEmpty(), - searchTerm = searchKeyword - ) + tracker.mapEvents.mapSearched( + locationType = locationInfo.locationType?.takeIf { it.isNotBlank() } ?: "unknown", + searchTerm = searchKeyword + )
- search_term는 사용자 입력 원문입니다. 개인정보/민감정보 취급 정책에 부합하는지(마스킹, 길이 제한 등) 보안/기획과 합의 여부를 확인해 주세요.
app/src/main/java/com/spoony/spoony/presentation/userpage/component/UserScreen.kt (1)
60-74: 프로필 조회 이벤트 발화 시점 적절. 차단 상태/진입 경로 속성 검토 권장
- profileId 키로 1회 발화되어 중복 방지 OK.
- isBlocked 프로필을 별도 속성으로 표기할지, 제외할지 제품/분석 스펙 합의가 필요합니다. 또한 진입 경로(entryPoint) 등 추가 속성은 추후 합의 시 확장해 주세요.
app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepTwoScreen.kt (1)
71-77: 이벤트는 네비게이션 전에 호출하는 것이 안전합니다.구성 해제 전 전송 보장을 위해 순서 교체를 권장합니다.
- onNextButtonClick = { - onNextButtonClick() - tracker.onboardingEvents.onboard2Completed( - isBirthdateEntered = !state.birth.isNullOrBlank(), - isActiveRegionEntered = state.region != null - ) - } + onNextButtonClick = { + tracker.onboardingEvents.onboard2Completed( + isBirthdateEntered = !state.birth.isNullOrBlank(), + isActiveRegionEntered = state.region != null + ) + onNextButtonClick() + }app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepOneScreen.kt (1)
57-60: 네비게이션 전 이벤트 호출 권장전송 보장을 위해 호출 순서 교체를 제안합니다.
- onButtonClick = { - onNextButtonClick() - tracker.onboardingEvents.onboard1Completed() - } + onButtonClick = { + tracker.onboardingEvents.onboard1Completed() + onNextButtonClick() + }app/src/main/java/com/spoony/spoony/core/designsystem/component/dialog/SpoonDrawDialog.kt (1)
93-94: 이벤트 로깅 위치를 검토하세요.
SpoonDrawDialogState.RESULT분기의 컴포저블 본문에서 직접 이벤트를 로깅하고 있습니다. 이는 리컴포지션 시마다 이벤트가 중복으로 전송될 수 있습니다.
LaunchedEffect블록 내에서 상태 전환 시 한 번만 로깅하는 것이 더 안전합니다.다음과 같이 수정하는 것을 권장합니다:
+ LaunchedEffect(dialogState) { + if (dialogState == SpoonDrawDialogState.RESULT) { + tracker.spoonDrawEvents.spoonReceived(drawResult.spoonAmount) + } + } + when (dialogState) { ... SpoonDrawDialogState.RESULT -> { - tracker.spoonDrawEvents.spoonReceived(drawResult.spoonAmount) - TitleButtonDialog(app/src/main/java/com/spoony/spoony/core/analytics/events/SpoonDrawEvents.kt (1)
9-18: JSON 문자열 직접 구성 방식을 개선해주세요.원시 JSON 문자열에 변수를 직접 삽입하는 방식은 유지보수가 어렵고 오류가 발생하기 쉽습니다. spoonCount가 Int이므로 현재는 안전하지만, 이 패턴이 String 타입에 사용될 경우 JSON 주입 위험이 있습니다.
다음과 같이 구조화된 방식으로 개선하는 것을 권장합니다:
- fun spoonReceived(spoonCount: Int) { - tracker.track( - eventName = "spoon_received", - properties = """ - { - "spoon_count": $spoonCount - } - """.trimIndent() - ) - } + fun spoonReceived(spoonCount: Int) { + val properties = mapOf( + "spoon_count" to spoonCount + ) + tracker.track( + eventName = "spoon_received", + properties = JSONObject(properties).toString() + ) + }또는 MixPanelTracker.track 메서드가 Map을 직접 받도록 오버로드를 추가하는 것이 더 좋습니다.
app/src/main/java/com/spoony/spoony/core/analytics/events/MypageEvents.kt (1)
10-18: JSONArray 사용은 올바른 접근입니다.JSONArray를 사용하여 리스트를 직렬화하는 것은 raw string interpolation보다 안전한 방식입니다. 하지만 다른 이벤트 클래스들과 일관성이 부족합니다.
코드베이스 전체에서 일관된 JSON 생성 방식을 사용하는 것을 권장합니다. 모든 이벤트 클래스에서 JSONObject/JSONArray를 사용하거나, MixPanelTracker.track 메서드가 Map을 직접 받도록 개선하는 것이 좋습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
app/build.gradle.kts(3 hunks)app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/AnalyticsEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ExploreEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MapEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MixPanelEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MixPanelUserProperties.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MypageEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/OnboardingEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/RegisterEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/SpoonDrawEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/designsystem/component/dialog/SpoonDrawDialog.kt(3 hunks)app/src/main/java/com/spoony/spoony/data/mapper/LocationMapper.kt(1 hunks)app/src/main/java/com/spoony/spoony/domain/entity/LocationEntity.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/MainActivity.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingEndScreen.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingScreen.kt(4 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepOneScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepThreeScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepTwoScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/auth/signin/SignInScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/explore/ExploreScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/explore/type/SortingOption.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt(7 hunks)app/src/main/java/com/spoony/spoony/presentation/exploreSearch/type/SearchType.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/follow/FollowRoute.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/follow/component/UserListScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/gourmet/map/MapScreen.kt(5 hunks)app/src/main/java/com/spoony/spoony/presentation/gourmet/map/model/LocationModel.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/gourmet/search/MapSearchScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt(12 hunks)app/src/main/java/com/spoony/spoony/presentation/profileedit/ProfileEditScreen.kt(4 hunks)app/src/main/java/com/spoony/spoony/presentation/profileedit/ProfileEditViewModel.kt(6 hunks)app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/register/RegisterScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/register/RegisterStartScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/splash/SplashScreen.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/splash/SplashSideEffect.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/splash/SplashViewModel.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/splash/model/UserModel.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/userpage/component/UserScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/userpage/mypage/MyPageRoute.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/userpage/otherpage/OtherPageRoute.kt(4 hunks)gradle/libs.versions.toml(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt (1)
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/component/ExploreSearchEmptyScreen.kt (1)
ExploreSearchEmptyScreen(22-58)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/ScoopDialog.kt (1)
ScoopDialog(17-40)
app/src/main/java/com/spoony/spoony/presentation/splash/SplashScreen.kt (1)
app/src/main/java/com/spoony/spoony/presentation/main/MainNavigator.kt (2)
navigateToMap(130-150)navigateToSignIn(100-108)
app/src/main/java/com/spoony/spoony/presentation/MainActivity.kt (1)
app/src/main/java/com/spoony/spoony/presentation/main/MainScreen.kt (1)
MainScreen(53-289)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PR Checker
🔇 Additional comments (23)
app/src/main/java/com/spoony/spoony/presentation/follow/FollowRoute.kt (1)
172-173: LGTM!
type파라미터가UserListScreen에 올바르게 전달되고 있습니다. 코드 구조가 명확하며 문제가 없습니다.app/src/main/java/com/spoony/spoony/data/mapper/LocationMapper.kt (1)
9-9: 직접 접근 안전:LocationResponseDto.locationType과 내부LocationType.locationTypeName은 DTO에서 non-null로 선언되어 있어 NPE 우려가 없습니다.app/src/main/java/com/spoony/spoony/presentation/MainActivity.kt (2)
19-20: LGTM! 애널리틱스 DI 연동이 적절합니다.Hilt를 통한
MixPanelEvents주입이 올바르게 구현되었습니다.
29-31: LGTM! CompositionLocal 제공이 적절합니다.
LocalTracker를 통해 하위 컴포저블에서 애널리틱스 트래커에 접근할 수 있도록 정확하게 구성되었습니다.app/src/main/java/com/spoony/spoony/presentation/gourmet/map/MapScreen.kt (3)
136-136: LGTM! 트래커 초기화가 적절합니다.
LocalTracker.current를 통한 트래커 접근이 올바르게 구현되었습니다.
181-181: LGTM! 탭 진입 이벤트 로깅이 적절합니다.기본 장소 목록을 로드하기 전에 "map" 탭 진입을 로깅하는 것이 적절합니다.
484-487: regionFilters에 전달되는 식별자 일관성 검토
MapScreen.kt (484–487)에서categoryName을 넘기고 있습니다. 애널리틱스 스펙에 따라categoryId를 사용해야 하는지 확인하고, 모든 화면에서 동일한 식별자를 전달하도록 조정하세요.app/src/main/java/com/spoony/spoony/presentation/profileedit/ProfileEditScreen.kt (3)
65-66: LGTM! 트래커 초기화가 적절합니다.화면 수준에서 트래커를 올바르게 초기화했습니다.
131-134: LGTM! 스푼 캐릭터 조회 이벤트 로깅이 적절합니다.사용자가 프로필 이미지 도움말을 조회할 때 이벤트를 정확하게 추적합니다.
206-211: LGTM! 프로필 업데이트 이벤트 로깅이 적절합니다.수정된 필드 정보와 함께 프로필 업데이트를 추적하는 것이 적절합니다.
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt (2)
78-78: LGTM! 트래커 초기화가 적절합니다.라우트 수준에서 트래커를 올바르게 초기화했습니다.
109-115: LGTM! 탐색 검색 이벤트 로깅이 적절합니다.검색 실행 후
searchType.trackingCode와 검색어를 포함하여 이벤트를 정확하게 로깅합니다. 기존 검색 로직을 유지하면서 애널리틱스 부가효과만 추가했습니다.app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingStepThreeScreen.kt (2)
26-26: LGTM! 트래커 초기화가 적절합니다.온보딩 라우트에서 트래커를 올바르게 초기화했습니다.
35-38: LGTM! 온보딩 완료 이벤트 로깅이 적절합니다.회원가입 실행과 함께 자기소개 길이를 포함한 온보딩 3단계 완료 이벤트를 정확하게 로깅합니다. 다른 온보딩 단계와 일관된 패턴입니다.
app/src/main/java/com/spoony/spoony/presentation/auth/onboarding/OnboardingScreen.kt (3)
51-51: LGTM! 트래커 초기화가 적절합니다.온보딩 화면에서 트래커를 올바르게 초기화했습니다.
84-84: LGTM! 온보딩 2단계 건너뛰기 이벤트 로깅이 적절합니다.사용자가 2단계를 건너뛸 때 이벤트를 정확하게 추적합니다.
96-96: LGTM! 온보딩 3단계 건너뛰기 이벤트 로깅이 적절합니다.사용자가 3단계를 건너뛸 때 이벤트를 정확하게 추적합니다.
app/src/main/java/com/spoony/spoony/presentation/splash/SplashSideEffect.kt (1)
3-6: LGTM! 사이드 이펙트 구조가 적절합니다.Sealed class와 data object를 사용한 타입 안전한 사이드 이펙트 정의가 훌륭합니다. 스플래시 화면의 네비게이션 흐름을 명확하게 표현합니다.
app/src/main/java/com/spoony/spoony/core/designsystem/component/dialog/SpoonDrawDialog.kt (1)
38-38: LGTM! 트래커 초기화가 적절합니다.다이얼로그 컴포저블 내에서 트래커를 올바르게 초기화했습니다.
app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt (1)
27-27: 분석 이벤트 통합이 올바르게 구현되었습니다.LocalTracker를 통한 이벤트 추적이 적절히 통합되었으며, 비즈니스 로직 실행 후 이벤트를 기록하는 패턴이 올바릅니다.
Also applies to: 52-52, 72-95
app/src/main/java/com/spoony/spoony/core/analytics/events/MixPanelUserProperties.kt (1)
6-11: 래퍼 클래스 구현이 깔끔합니다.MixPanelTracker를 간결하게 감싼 구조이며, Map<String, Any> 타입을 사용하여 타입 안전성을 제공합니다.
app/src/main/java/com/spoony/spoony/presentation/explore/ExploreScreen.kt (1)
48-48: 탐색 화면에 분석 이벤트가 적절히 통합되었습니다.LaunchedEffect를 사용한 탭 진입 추적과 정렬 옵션 선택 추적이 올바르게 구현되었습니다.
Also applies to: 93-93, 98-100, 135-138
app/src/main/java/com/spoony/spoony/presentation/userpage/otherpage/OtherPageRoute.kt (1)
14-14: 사용자 페이지 분석 이벤트가 올바르게 구현되었습니다.팔로우/언팔로우 및 필터 적용 이벤트 추적이 적절하게 통합되었으며, entryPoint 매개변수도 올바르게 전달됩니다.
Also applies to: 39-39, 79-102
| "String", | ||
| "MIXPANEL_KEY", | ||
| properties["mixpanelDevKey"] as? String ?: "" | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빈 키 fallback이 BuildConfig 생성을 깨뜨립니다.
buildConfigField의 세 번째 인자는 리터럴이어야 하는데, fallback으로 전달한 ""는 따옴표가 없어 public static final String MIXPANEL_KEY = ; 형태로 생성돼 바로 컴파일 에러가 납니다. 최소한 "" 대신 "\"\""을 넘기거나, 값이 없을 때는 명시적으로 에러를 던지도록 처리해 주세요.
buildConfigField(
"String",
"MIXPANEL_KEY",
- properties["mixpanelDevKey"] as? String ?: ""
+ (properties["mixpanelDevKey"] as? String) ?: "\"\""
)
@@
buildConfigField(
"String",
"MIXPANEL_KEY",
- properties["mixpanelProdKey"] as? String ?: ""
+ (properties["mixpanelProdKey"] as? String) ?: "\"\""
)Also applies to: 72-75
🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 58-61 (and similarly lines 72-75), the
third argument to buildConfigField must be a literal Java expression; passing an
unquoted empty string from the Kotlin expression produces invalid generated code
(e.g. public static final String MIXPANEL_KEY = ;). Change the call to provide a
properly quoted string literal when the property is absent (e.g. return "\"\""
for empty) or explicitly throw a Gradle exception when the required property is
missing so buildConfigField always receives a valid literal; apply the same fix
to the other occurrence at lines 72-75.
app/src/main/java/com/spoony/spoony/core/analytics/events/AnalyticsEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/core/analytics/events/ExploreEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/core/analytics/events/ExploreEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/core/analytics/events/RegisterEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt
Show resolved
Hide resolved
app/src/main/java/com/spoony/spoony/presentation/userpage/mypage/MyPageRoute.kt
Outdated
Show resolved
Hide resolved
…anel # Conflicts: # app/src/main/java/com/spoony/spoony/presentation/explore/ExploreScreen.kt # app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt # gradle/libs.versions.toml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (4)
app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt (1)
13-219: JSON 직렬화 방식을 안전하게 바꿔주세요문자열 보간으로 JSON을 만드는 현재 방식은
tabName,placeName,entryPoint등에 따옴표·줄바꿈 같은 문자가 들어오면 바로 깨진 JSON이 되어 Mixpanel 적재가 실패합니다. 이전 리뷰에서 이미 동일 지적이 있었는데 아직 반영되지 않았습니다.JSONObject(또는mutableMapOf→JSONObject)에 값을 넣은 뒤toString()으로 넘기도록 전체 이벤트를 교체해 주세요. 예시는 아래와 같습니다.+import org.json.JSONObject ... - tracker.track( - eventName = "tab_entered", - properties = """ - { - "tab_name": "$tabName" - } - """.trimIndent() - ) + val properties = JSONObject().apply { + put("tab_name", tabName) + } + tracker.track( + eventName = "tab_entered", + properties = properties.toString() + )다른 이벤트들도 같은 방식으로 정리해야 안전하게 직렬화됩니다.
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
143-157: 재구성마다 review_viewed 이벤트가 반복 전송됩니다Composable 본문에서 바로
tracker.commonEvents.reviewViewed를 호출하고 있어 재구성마다 이벤트가 중복 적재됩니다. 이전 리뷰에서LaunchedEffect(postId)등으로 감싸 달라는 피드백이 있었는데 아직 반영되지 않았습니다. 아래처럼 사이드이펙트 스코프로 옮겨 한 번만 호출되도록 수정해 주세요.- tracker.commonEvents.reviewViewed( - reviewId = postId, - ... - ) + LaunchedEffect(postId) { + tracker.commonEvents.reviewViewed( + reviewId = postId, + ... + ) + }
postId외에 의존 값이 필요하면 키에 추가로 넣어주면 됩니다.app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt (1)
22-175: 이전 리뷰에서 지적된 JSON 직렬화 취약점이 아직 수정되지 않았습니다.
placeName,category등의 문자열 속성에 따옴표, 백슬래시, 개행 등이 포함될 경우 문자열 보간 방식으로 생성한 JSON이 깨져 Mixpanel로 이벤트가 전송되지 않습니다. 모든 메서드(spoonUseIntent,spoonUsed,placeMapSaved,placeMapRemoved,directionClicked)에서 동일한 문제가 발생합니다.이전 리뷰에서 제안된 대로
JSONObject또는Map기반 접근으로 안전하게 직렬화하도록 수정해 주세요.다음 diff를 적용하여 안전한 JSON 빌더를 사용하도록 변경하세요:
import com.spoony.spoony.core.analytics.MixPanelTracker import jakarta.inject.Inject +import org.json.JSONObject class ReviewDetailEvents @Inject constructor( private val tracker: MixPanelTracker ) { fun spoonUseIntent( reviewId: Int, authorUserId: Int, placeName: String, category: String, menuCount: Int, satisfactionScore: Double, reviewLength: Int, photoCount: Int, hasDisappointment: Boolean, savedCount: Int, isFollowingAuthor: Boolean ) { + val properties = JSONObject().apply { + put("review_id", reviewId) + put("author_user_id", authorUserId) + put("place_name", placeName) + put("category", category) + put("menu_count", menuCount) + put("satisfaction_score", satisfactionScore) + put("review_length", reviewLength) + put("photo_count", photoCount) + put("has_disappointment", hasDisappointment) + put("saved_count", savedCount) + put("is_following_author", isFollowingAuthor) + } tracker.track( eventName = "spoon_use_intent", - properties = """ - { - "review_id" : $reviewId, - "author_user_id" : $authorUserId, - "place_name" : "$placeName", - "category" : "$category", - "menu_count" : $menuCount, - "satisfaction_score" : $satisfactionScore, - "review_length" : $reviewLength, - "photo_count" : $photoCount, - "has_disappointment" : $hasDisappointment, - "saved_count" : $savedCount, - "is_following_author" : $isFollowingAuthor - } - """.trimIndent() + properties = properties.toString() ) }나머지 메서드들(
spoonUsed,placeMapSaved,placeMapRemoved,directionClicked)도 동일한 패턴으로 수정 필요합니다.app/build.gradle.kts (1)
61-64: 빈 문자열 fallback으로 다시 빌드 깨집니다.
buildConfigField에 넘기는 값은 유효한 자바 리터럴이어야 하는데, 현재 분기에서는 키가 없을 때""(따옴표 없는 빈 문자열) 그대로 전달하고 있어서public static final String MIXPANEL_KEY = ;형태가 생성됩니다. 이전 코멘트와 동일한 문제이므로, 최소한\"\"로 감싸 주거나 아예 필수 프로퍼티로 강제해 즉시 실패하도록 처리해 주세요. 디버그/릴리스 블록 모두 동일하게 수정이 필요합니다.- buildConfigField( - "String", - "MIXPANEL_KEY", - properties["mixpanelDevKey"] as? String ?: "" - ) + val mixpanelDevKey = properties["mixpanelDevKey"] as? String + ?: error("`mixpanelDevKey`가 설정돼 있지 않습니다.") + buildConfigField("String", "MIXPANEL_KEY", "\"$mixpanelDevKey\"") @@ - buildConfigField( - "String", - "MIXPANEL_KEY", - properties["mixpanelProdKey"] as? String ?: "" - ) + val mixpanelProdKey = properties["mixpanelProdKey"] as? String + ?: error("`mixpanelProdKey`가 설정돼 있지 않습니다.") + buildConfigField("String", "MIXPANEL_KEY", "\"$mixpanelProdKey\"")Also applies to: 75-78
🧹 Nitpick comments (2)
app/src/main/java/com/spoony/spoony/presentation/register/model/PlaceReviewModel.kt (1)
22-23: 새 필드의 용도를 문서화하세요.
addMapCount필드가 추가되었지만, 이름만으로는 정확한 용도를 파악하기 어렵습니다. KDoc 주석을 추가하여 이 필드가 무엇을 추적하는지, -1 기본값의 의미(예: 초기화되지 않음)를 명시하는 것을 권장합니다.예시:
data class PlaceReviewModel( val reviewId: Int, val userId: Int, val photoUrls: ImmutableList<String>, val date: String, val menuList: ImmutableList<String>, val description: String, val value: Double, val cons: String?, val placeName: String, val placeAddress: String, val latitude: Double, val longitude: Double, val category: CategoryState, + /** 지도에 추가된 횟수. -1은 초기화되지 않은 상태를 나타냄 */ val addMapCount: Int )app/src/main/java/com/spoony/spoony/presentation/register/model/RegisterState.kt (1)
27-30: 새 필드에 KDoc 주석 추가 권장
userId와addMapCount기본값 -1의 의미(초기화되지 않음)를 명시하는 KDoc 주석 추가- (선택) analytics/트래킹 로직에서 해당 값이 그대로 전송되지 않도록 처리 여부 검토
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
.github/workflows/pr_checker.yml(2 hunks)app/build.gradle.kts(3 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/explore/ExploreScreen.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/explore/component/ExploreFilterSection.kt(4 hunks)app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt(7 hunks)app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt(12 hunks)app/src/main/java/com/spoony/spoony/presentation/placeDetail/model/PlaceDetailModel.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/register/model/PlaceReviewModel.kt(3 hunks)app/src/main/java/com/spoony/spoony/presentation/register/model/RegisterState.kt(1 hunks)gradle/libs.versions.toml(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- .github/workflows/pr_checker.yml
- app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/ScoopDialog.kt (1)
ScoopDialog(17-40)
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt (1)
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/component/ExploreSearchEmptyScreen.kt (1)
ExploreSearchEmptyScreen(22-58)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PR Checker
🔇 Additional comments (4)
app/src/main/java/com/spoony/spoony/presentation/exploreSearch/ExploreSearchScreen.kt (2)
42-42: LGTM! 애널리틱스 통합 준비가 올바릅니다.CompositionLocal을 통해 tracker를 정확하게 주입하고 있습니다.
Also applies to: 124-125
173-176: LGTM! 검색 이벤트 트래킹이 잘 구현되었습니다.검색 액션 시점에
exploreSearched이벤트를 적절히 로깅하고 있으며,searchType.trackingCode와searchText를 올바르게 전달하고 있습니다.app/src/main/java/com/spoony/spoony/presentation/register/model/PlaceReviewModel.kt (2)
40-41: LGTM!
addMapCount필드 매핑이 올바르게 구현되었습니다. null 값에 대한 -1 기본값 처리도 다른 필드들의 패턴과 일관성 있게 적용되었습니다.
61-63: RegisterState 기본값(-1) 처리 검증
userId,addMapCount가 RegisterState로 올바르게 복사됨을 확인했습니다.- 초기값
-1이 UI나 비즈니스 로직에서 노출되지 않도록 RegisterScreen/ViewModel 단에서 기본값 대체(예: 0) 또는 guard 처리 여부를 검증하세요.
There was a problem hiding this 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
♻️ Duplicate comments (4)
app/src/main/java/com/spoony/spoony/core/analytics/events/MapEvents.kt (1)
1-22: 이전 리뷰 이슈가 해결되었습니다!이전 리뷰에서 지적된 JSON 직렬화 문제가
JSONObject를 사용하여 안전하게 해결되었습니다. 사용자 입력이 포함될 수 있는locationType과searchTerm이 이제 안전하게 이스케이프되어 전송됩니다.app/src/main/java/com/spoony/spoony/core/analytics/events/ExploreEvents.kt (1)
1-31: 이전 리뷰 이슈가 해결되었습니다!이전 리뷰에서 지적된 JSON 주입 위험이
JSONObject를 사용하여 안전하게 해결되었습니다. 특히 사용자 입력인searchTerm이 안전하게 이스케이프되어 전송됩니다.app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
136-158: 이전 리뷰 이슈가 해결되었습니다!이전 리뷰에서 지적된 재구성 시 중복 트래킹 문제가
LaunchedEffect를 사용하여 해결되었습니다.state.reviewId와userProfile.userId를 키로 사용하여 필요한 경우에만 이벤트가 발생합니다.app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt (1)
1-168: 이전 리뷰 이슈가 해결되었습니다!이전 리뷰에서 지적된 JSON 직렬화 문제가
JSONObject를 사용하여 안전하게 해결되었습니다. 사용자 데이터가 포함된placeName등의 속성이 안전하게 이스케이프되어 전송됩니다.
🧹 Nitpick comments (3)
app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt (1)
10-167: 코드 중복을 줄일 수 있습니다.5개의 메서드(
spoonUseIntent,spoonUsed,placeMapSaved,placeMapRemoved,directionClicked)가 동일한 11개의 파라미터와 유사한 JSONObject 빌딩 로직을 공유하고 있습니다. 공통 속성을 data class로 추출하면 유지보수가 쉬워집니다.다음과 같이 리팩토링을 고려해 보세요:
data class ReviewDetailProperties( val reviewId: Int, val authorUserId: Int, val placeName: String, val category: String, val menuCount: Int, val satisfactionScore: Double, val reviewLength: Int, val photoCount: Int, val hasDisappointment: Boolean, val savedCount: Int, val isFollowingAuthor: Boolean ) { fun toJSONObject() = JSONObject().apply { put("review_id", reviewId) put("author_user_id", authorUserId) put("place_name", placeName) put("category", category) put("menu_count", menuCount) put("satisfaction_score", satisfactionScore) put("review_length", reviewLength) put("photo_count", photoCount) put("has_disappointment", hasDisappointment) put("saved_count", savedCount) put("is_following_author", isFollowingAuthor) } } fun spoonUseIntent(properties: ReviewDetailProperties) { tracker.track( eventName = "spoon_use_intent", properties = properties.toJSONObject() ) }이렇게 하면 속성 이름 변경이나 새 속성 추가 시 한 곳만 수정하면 됩니다.
app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountDeleteScreen.kt (1)
52-57: 에러 처리 추가 권장
resetUserProfile()이 실패하더라도 앱 재시작은 진행되어야 할 수 있습니다. 분석 호출이 실패해도 사용자 흐름이 차단되지 않도록 try-catch로 감싸는 것을 고려해주세요.LaunchedEffect(viewModel.restartTrigger, lifecycleOwner) { viewModel.restartTrigger.flowWithLifecycle(lifecycleOwner.lifecycle).collect { effect -> - tracker.userProperties.resetUserProfile() + runCatching { + tracker.userProperties.resetUserProfile() + } ProcessPhoenix.triggerRebirth(context) } }app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountManagementScreen.kt (1)
43-43: 코드 중복 - 공통 유틸리티 함수 추출 권장
AccountDeleteScreen.kt와 동일한 패턴(LocalTracker 가져오기 + LaunchedEffect에서 resetUserProfile 호출)이 반복됩니다. 재사용 가능한 composable 또는 확장 함수로 추출하면 유지보수성이 향상됩니다.예시로 공통 함수를 만들 수 있습니다:
// 새 파일: AccountUtils.kt @Composable fun rememberAccountResetEffect( restartTrigger: Flow<Unit>, tracker: MixPanelTracker = LocalTracker.current ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current LaunchedEffect(restartTrigger, lifecycleOwner) { restartTrigger.flowWithLifecycle(lifecycleOwner.lifecycle).collect { runCatching { tracker.userProperties.resetUserProfile() } ProcessPhoenix.triggerRebirth(context) } } }그런 다음 두 화면에서 다음과 같이 사용:
rememberAccountResetEffect(viewModel.restartTrigger)참고:
AccountDeleteScreen.kt의 리뷰 코멘트에서 언급한 에러 처리도 함께 적용되었습니다.Also applies to: 45-50
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/AnalyticsEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ExploreEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MapEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MixPanelUserProperties.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/MypageEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/OnboardingEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/RegisterEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/SpoonDrawEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt(12 hunks)app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountDeleteScreen.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountManagementScreen.kt(2 hunks)app/src/main/java/com/spoony/spoony/presentation/userpage/mypage/MyPageRoute.kt(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- app/src/main/java/com/spoony/spoony/core/analytics/events/MixPanelUserProperties.kt
- app/src/main/java/com/spoony/spoony/core/analytics/events/RegisterEvents.kt
- app/src/main/java/com/spoony/spoony/core/analytics/events/MypageEvents.kt
- app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt
- app/src/main/java/com/spoony/spoony/core/analytics/events/AnalyticsEvents.kt
- app/src/main/java/com/spoony/spoony/core/analytics/events/OnboardingEvents.kt
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/ScoopDialog.kt (1)
ScoopDialog(17-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PR Checker
🔇 Additional comments (5)
app/src/main/java/com/spoony/spoony/core/analytics/events/SpoonDrawEvents.kt (1)
1-18: LGTM!코드가 간결하고 안전하게 구현되어 있습니다.
JSONObject를 사용하여 속성을 안전하게 직렬화하고 있습니다.app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt (1)
46-53: 에러 처리가 잘 되어 있습니다.JSON 파싱 실패 시 빈
JSONObject로 안전하게 폴백하고 에러를 로깅하는 방식이 적절합니다. 이벤트 전송 실패를 방지하면서도 디버깅을 위한 정보를 남깁니다.app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
353-370: 새로운 트래킹 파라미터가 적절하게 추가되었습니다.
trackSpoonUseIntent와trackSpoonUseFailed파라미터가PlaceDetailScreen에 추가되어 스푼 사용 의도와 실패를 추적할 수 있게 되었습니다. 이벤트 호출 시점도 적절합니다(Lines 502, 505).app/src/main/java/com/spoony/spoony/presentation/userpage/mypage/MyPageRoute.kt (2)
53-54: 에러 핸들링이 적절하게 추가되었습니다.기존
ShowSnackbar패턴과 일관성 있게 에러 처리가 구현되었습니다.
60-64: 이전 리뷰 이슈가 올바르게 해결되었습니다.
userPageState.userType을 키로 사용하는 별도의LaunchedEffect로 분리하여, 초기 컴포지션 시점의 스냅샷에만 의존하던 문제가 해결되었습니다. 이제userType이MY_PAGE로 변경될 때마다 트래킹 이벤트가 정상적으로 발생합니다.
| ) { | ||
| val context = LocalContext.current | ||
| val lifecycleOwner = LocalLifecycleOwner.current | ||
| val tracker = LocalTracker.current |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
LocalTracker 제공 여부 확인 필요
LocalTracker.current는 컴포지션에서 tracker가 제공되지 않으면 런타임 오류가 발생할 수 있습니다. Preview 함수나 테스트 환경에서 이 화면이 독립적으로 렌더링될 때 문제가 될 수 있습니다.
다음 스크립트로 LocalTracker가 모든 호출 지점에서 제공되는지 확인해주세요:
🏁 Script executed:
#!/bin/bash
# LocalTracker.current를 사용하는 모든 화면이 CompositionLocalProvider로 감싸져 있는지 확인
# AccountDeleteScreen과 AccountManagementScreen이 호출되는 곳 찾기
rg -nP --type=kotlin -C5 'AccountDeleteScreen\s*\(' -g '!**/test/**'
rg -nP --type=kotlin -C5 'AccountManagementScreen\s*\(' -g '!**/test/**'
# LocalTracker를 provide하는 곳 찾기
rg -nP --type=kotlin -C3 'CompositionLocalProvider.*LocalTracker' -g '!**/test/**'Length of output: 446
LocalTracker.current가 제공되지 않으면 런타임 오류가 발생할 수 있습니다. 다음 스크립트로 .kt 파일 전체에서 사용 위치와 CompositionLocalProvider(LocalTracker) 제공 위치를 재확인해주세요:
🏁 Script executed:
#!/bin/bash
# 모든 .kt 파일에서 LocalTracker.current 사용 위치 확인
rg -n 'LocalTracker\.current' -g '*.kt'
# 모든 .kt 파일에서 CompositionLocalProvider 내 LocalTracker 제공 위치 확인
rg -nP 'CompositionLocalProvider.*LocalTracker' -g '*.kt'
# AccountDeleteScreen 및 AccountManagementScreen 호출 위치 확인
rg -n 'AccountDeleteScreen\(' -g '*.kt'
rg -n 'AccountManagementScreen\(' -g '*.kt'Length of output: 4005
다음 스크립트로 AccountDeleteScreen.kt 상단(1–200행)에 @Preview 선언이 있는지 확인해주세요:
🏁 Script executed:
#!/bin/bash
sed -n '1,200p' app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountDeleteScreen.ktLength of output: 5129
AccountDeleteScreenPreview에 LocalTracker 제공 필요
AccountDeleteScreenPreview가 CompositionLocalProvider(LocalTracker) 없이 호출되어 런타임 오류가 발생할 수 있습니다. Preview 함수 래핑 시 다음 중 하나를 적용하세요:
CompositionLocalProvider(LocalTracker provides mixPanelEvents) { AccountDeleteScreen(...) }LocalTracker에 기본값(예:DefaultTracker) 제공
🤖 Prompt for AI Agents
In
app/src/main/java/com/spoony/spoony/presentation/setting/account/AccountDeleteScreen.kt
around line 50, AccountDeleteScreenPreview calls AccountDeleteScreen without
providing LocalTracker which can cause a runtime error; update the preview to
wrap the composable with CompositionLocalProvider(LocalTracker provides a mock
or real tracker instance) or give LocalTracker a sensible default (e.g.,
DefaultTracker) so the CompositionLocal is always available during previews and
runtime.
Roel4990
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
와우 고생 많으셨습니다. 하나하나 잘 구조화 하신것 같네요. 한가지 궁금한 부분은 믹스패널 이벤트들을 코어 안에 두셨더라구요. 저라면 사용하는 각 폴더 페이지 안에 events 를 만들어서 넣었을 것 같은데 core 안에 다 두셨네요. 생각해보니 화면별로 만든다면 유지보수가 힘들 것 같아 모으는게 더 좋아보이기도 합니다. 좋은 코드 보고 갑니다.
각 폴더 페이지라고 하면 feature쪽 말씀하시는거 맞죠??? 그렇게 할까도 잠깐 고민했었는데 말씀하신대로 이벤트들이 다 흩어져있어서 관리하기가 힘들고 |
angryPodo
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
대공사 고생만앗수~~~🚀🚀🚀
| fun reviewViewed( | ||
| reviewId: Int, | ||
| authorUserId: Int, | ||
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Double, | ||
| reviewLength: Int, | ||
| photoCount: Int, | ||
| hasDisappointment: Boolean, | ||
| savedCount: Int, | ||
| isSelfReview: Boolean, | ||
| isFollowedUserReview: Boolean, | ||
| isSavedReview: Boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런식으로 파라미터가 10개가 넘어가는 경우가 꽤 있어요. 어떤 값이 어떤 파라미터에 해당하는지 한번에 파악하기 어렵다고 생각이 들어요.
data class로 만들어서 객체로 전달하는건 어떻게 생각하십니까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
흠냐냐 사실 이벤트를 위한 모델을 만드는 것에 대해 조금 많이 회의적이긴 한데요ㅎㅎ 겹치는 파라미터가 너무 많기도 하고 해서 일단 가장 많이 겹치는 파라미터만 data class로 만들었습니다!!
| fun track(eventName: String) { | ||
| Timber.tag("mixpanel").d(eventName) | ||
| mixpanel.track(eventName) | ||
| } | ||
|
|
||
| fun track(eventName: String, properties: String) { | ||
| Timber.tag("mixpanel").d("$eventName $properties") | ||
| mixpanel.track(eventName, properties.toJsonObject()) | ||
| } | ||
|
|
||
| fun track(eventName: String, properties: JSONObject) { | ||
| Timber.tag("mixpanel").d("$eventName $properties") | ||
| mixpanel.track(eventName, properties) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 Events 클래스들에서 모두 JSONObject를 직접 만들어 track 함수를 호출하고 있어요. String 타입의 properties를 받는 오버로딩은 내부에서 바로 JSONObject로 변환하는데 track(eventName: String, properties: String) 오버로딩을 제거해서 MixPanelTracker의 인터페이스를 더 단순하게 만드는 게 지금 기획의 요구에서 가능할까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요건 구현 방식을 바꿔서 2개가 생긴건데요 혹시 나중에 쓸일이 있을까 싶어서 놔두긴 했는데 뭐.. 필요한 사람이 다시 만드는걸로 하죠ㅋㅋ
| package com.spoony.spoony.core.analytics.events | ||
|
|
||
| import com.spoony.spoony.core.analytics.MixPanelTracker | ||
| import jakarta.inject.Inject |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
javax.inject.Inject 이거랑 혼용이 있어요! 하나로 통일해주세요!!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이벤트 관련 파일들만 javax -> jakarta로 수정할게요~~
| placeName: String, | ||
| category: String, | ||
| menuCount: Int, | ||
| satisfactionScore: Double, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 Float 타입인것도 있더라구요 (e.g. satisfactionScore: Double) 통일하면 곤란한거일까요??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 이거 왜 이렇게 됐는지 알아버렸어요... 지금 reviewEdited()만 Float이고 나머지는 Double인데요! registerState에 있는 userSatisfactionValue가 Float타입이더라구요😅 명세서 확인해보니 dto도 Double이라 아요에서도 Double로 전달할 것 같아서 요건 형변환해서 보내는걸로 하겠습니다!
| fun updateNickname(nickname: String) { | ||
| _profileEditModel.update { it.copy(userName = nickname) } | ||
| updateSaveButtonState() | ||
| _fieldsUpdated.add("nickname") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요거 문자열 Enum으로 하면 어떨까요~?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
♻️ Duplicate comments (4)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
137-161: 재구성 시 중복 트래킹 방지 처리 확인 — 잘 반영됨
LaunchedEffect(state.reviewId, userProfile.userId)로review_viewed를 1회성으로 보낸 점 좋습니다. 과거 중복 적재 지적 사항이 해결되었습니다.app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt (1)
5-5: Hilt 호환성 확인:jakarta.inject.Inject사용본 파일도
jakarta.inject.Inject를 사용합니다. 프로젝트의 Hilt/Dagger 버전이 Jakarta 어노테이션을 지원하는지 확인이 필요합니다. 미지원 시javax.inject.Inject로 교체해 주세요.app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt (2)
5-5: Hilt 호환성 확인:jakarta.inject.Inject사용프로젝트 전반의 DI 어노테이션 일관성/호환성 확인이 필요합니다. 미지원 버전이면
javax.inject.Inject로 교체해 주세요.
12-19: 문자열 보간 → JSONObject 전환 잘 하셨습니다기존 문자열 보간 JSON 문제를 해소했고, 안전한 직렬화로 수정된 점 좋습니다.
🧹 Nitpick comments (8)
app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt (2)
20-25: 첫 로그인 식별 병합(alias) 고려기존 익명 distinct_id 이벤트를 로그인 후 사용자 ID로 합치려면 최초 로그인 시점에
alias(userId)를 한 번 호출한 뒤identify(userId)를 사용하는 패턴이 일반적입니다. 사용자 여정 분석 정확도를 높이려면 별도 진입점(예: Auth 성공 시)에서 alias를 처리하는 유틸을 도입해 주세요.
7-7: Hilt v2.57.2은jakarta.inject.Inject를 지원합니다
- Dagger/Hilt 2.57.x부터 Jakarta 애노테이션을 공식 지원하므로
jakarta.inject.Inject사용은 안전합니다.- 코드베이스 전반의 일관성을 위해
jakarta.inject.Inject또는javax.inject.Inject중 하나로 통일하세요.app/src/main/java/com/spoony/spoony/core/analytics/model/ReviewTrackingModel.kt (1)
3-14: 모델 정의 적절 — 공통 필드 캡슐화 LGTM리뷰 관련 공통 컨텍스트를 한 곳에 모아 재사용성이 좋습니다. 추후 백엔드 ID 스펙이 64비트로 확대될 경우를 대비해
reviewId/authorUserId의Long전환 가능성만 염두에 두면 좋겠습니다.app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (2)
168-168: 비지역return로 화면이 비어질 수 있음
val postId = ... ?: return은 해당 컴포저블 전체를 빠져나가 UI가 렌더링되지 않을 수 있습니다. UI는 유지하되, 포스트 ID가 필요한 액션/이벤트만 가드하는 방식으로 바꾸는 것을 권장합니다.
141-159:ReviewTrackingModel생성 중복 제거동일한 모델을 여러 번 구성하고 있어 유지보수/오타 리스크가 큽니다. 성공 분기에서 한 번 계산해 재사용하세요. 또한
(state.reviewId as UiState.Success).data캐스팅도 반복 대신postId를 사용하세요.예시 패턴:
- val postId = (state.reviewId as? UiState.Success)?.data ?: return + val postId = (state.reviewId as? UiState.Success)?.data ?: return + val tracking = ReviewTrackingModel( + reviewId = postId, + authorUserId = userProfile.userId, + placeName = uiState.data.placeName, + category = uiState.data.category.categoryName, + menuCount = uiState.data.menuList.size, + satisfactionScore = uiState.data.value, + reviewLength = uiState.data.description.length, + photoCount = uiState.data.photoUrlList.size, + hasDisappointment = uiState.data.cons.isNotEmpty(), + savedCount = state.addMapCount + ) ... - tracker.reviewDetailEvents.spoonUsed( - reviewTrackingModel = ReviewTrackingModel( ... ), - isFollowingAuthor = state.isFollowing - ) + tracker.reviewDetailEvents.spoonUsed( + reviewTrackingModel = tracking, + isFollowingAuthor = state.isFollowing + )위 패턴을 다른 이벤트 호출에도 동일 적용해 주세요.
Also applies to: 176-189, 237-251, 263-276, 281-294, 327-356, 369-383
app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt (1)
15-30: 프로퍼티 빌드 중복 — 헬퍼로 추출동일 키 매핑이 반복됩니다. 공통 빌더로 추출하면 유지보수가 쉬워집니다.
예시:
class ReviewDetailEvents @Inject constructor( private val tracker: MixPanelTracker ) { + private fun buildProps(model: ReviewTrackingModel, isFollowingAuthor: Boolean? = null) = + JSONObject().apply { + put("review_id", model.reviewId) + put("author_user_id", model.authorUserId) + put("place_name", model.placeName) + put("category", model.category) + put("menu_count", model.menuCount) + put("satisfaction_score", model.satisfactionScore) + put("review_length", model.reviewLength) + put("photo_count", model.photoCount) + put("has_disappointment", model.hasDisappointment) + put("saved_count", model.savedCount) + if (isFollowingAuthor != null) put("is_following_author", isFollowingAuthor) + } fun spoonUseIntent(reviewTrackingModel: ReviewTrackingModel, isFollowingAuthor: Boolean) { - tracker.track(eventName = "spoon_use_intent", properties = JSONObject().apply { ... }) + tracker.track("spoon_use_intent", buildProps(reviewTrackingModel, isFollowingAuthor)) } fun spoonUsed(reviewTrackingModel: ReviewTrackingModel, isFollowingAuthor: Boolean) { - tracker.track(eventName = "spoon_used", properties = JSONObject().apply { ... }) + tracker.track("spoon_used", buildProps(reviewTrackingModel, isFollowingAuthor)) } fun placeMapSaved(reviewTrackingModel: ReviewTrackingModel, isFollowingAuthor: Boolean) { - tracker.track(eventName = "place_map_saved", properties = JSONObject().apply { ... }) + tracker.track("place_map_saved", buildProps(reviewTrackingModel, isFollowingAuthor)) } fun placeMapRemoved(reviewTrackingModel: ReviewTrackingModel, isFollowingAuthor: Boolean) { - tracker.track(eventName = "place_map_removed", properties = JSONObject().apply { ... }) + tracker.track("place_map_removed", buildProps(reviewTrackingModel, isFollowingAuthor)) } fun directionClicked(reviewTrackingModel: ReviewTrackingModel, isFollowingAuthor: Boolean) { - tracker.track(eventName = "direction_clicked", properties = JSONObject().apply { ... }) + tracker.track("direction_clicked", buildProps(reviewTrackingModel, isFollowingAuthor)) } }이벤트명/키 문자열도 상수로 모듈 내에 모아두면 오타를 방지할 수 있습니다.
Also applies to: 37-52, 63-78, 85-100, 107-122
app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt (2)
27-45:ReviewTrackingModel→ JSON 매핑 중복 제거여러 메서드에서 동일 매핑이 반복됩니다. 공통 헬퍼로 추출해 중복을 제거하세요.
예시:
class CommonEvents @Inject constructor( private val tracker: MixPanelTracker ) { + private fun reviewProps(m: ReviewTrackingModel) = JSONObject().apply { + put("review_id", m.reviewId) + put("author_user_id", m.authorUserId) + put("place_name", m.placeName) + put("category", m.category) + put("menu_count", m.menuCount) + put("satisfaction_score", m.satisfactionScore) + put("review_length", m.reviewLength) + put("photo_count", m.photoCount) + put("has_disappointment", m.hasDisappointment) + put("saved_count", m.savedCount) + } fun reviewViewed(reviewTrackingModel: ReviewTrackingModel, isSelfReview: Boolean, isFollowedUserReview: Boolean, isSavedReview: Boolean) { - tracker.track(eventName = "review_viewed", properties = JSONObject().apply { ... }) + val props = reviewProps(reviewTrackingModel).apply { + put("is_self_review", isSelfReview) + put("is_followed_user_review", isFollowedUserReview) + put("is_saved_review", isSavedReview) + } + tracker.track("review_viewed", props) } fun reviewEdited(reviewTrackingModel: ReviewTrackingModel) { - tracker.track(eventName = "review_edited", properties = JSONObject().apply { ... }) + tracker.track("review_edited", reviewProps(reviewTrackingModel)) } fun followUserFromReview(reviewTrackingModel: ReviewTrackingModel) { - tracker.track(eventName = "follow_user_from_review", properties = JSONObject().apply { ... }) + tracker.track("follow_user_from_review", reviewProps(reviewTrackingModel).apply { put("entry_point", "review") }) } fun unfollowUserFromReview(reviewTrackingModel: ReviewTrackingModel) { - tracker.track(eventName = "unfollow_user_from_review", properties = JSONObject().apply { ... }) + tracker.track("unfollow_user_from_review", reviewProps(reviewTrackingModel).apply { put("entry_point", "review") }) } }Also applies to: 50-64, 112-128, 134-149
83-94:entryPoint문자열 하드코딩 대신 타입화 권장
entryPoint는 오타/불일치가 생기기 쉬우므로enum class EntryPoint { Review, ... }등으로 타입을 고정하면 안정적입니다. 직렬화 시name.lowercase()등으로 변환하세요.Also applies to: 96-107
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/CommonEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/events/ReviewDetailEvents.kt(1 hunks)app/src/main/java/com/spoony/spoony/core/analytics/model/ReviewTrackingModel.kt(1 hunks)app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt(12 hunks)app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt(3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/PlaceDetailRoute.kt (1)
app/src/main/java/com/spoony/spoony/presentation/placeDetail/component/ScoopDialog.kt (1)
ScoopDialog(17-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PR Builder
| fun track(eventName: String) { | ||
| Timber.tag("mixpanel").d(eventName) | ||
| mixpanel.track(eventName) | ||
| } | ||
|
|
||
| fun track(eventName: String, properties: JSONObject) { | ||
| Timber.tag("mixpanel").d("$eventName $properties") | ||
| mixpanel.track(eventName, properties) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Release 로그에 PII 노출 위험: Timber에 이벤트/프로퍼티 출력
review_id, author_user_id, place_name 등 민감 정보가 릴리스에서도 Logcat으로 노출될 수 있습니다. 디버그에서만 로깅하거나 완전히 제거해 주세요.
아래처럼 디버그 빌드에서만 로깅하도록 가드하는 것을 권장합니다:
import org.json.JSONObject
import timber.log.Timber
+import com.spoony.spoony.BuildConfig
...
fun track(eventName: String) {
- Timber.tag("mixpanel").d(eventName)
+ if (BuildConfig.DEBUG) {
+ Timber.tag("mixpanel").d(eventName)
+ }
mixpanel.track(eventName)
}
fun track(eventName: String, properties: JSONObject) {
- Timber.tag("mixpanel").d("$eventName $properties")
+ if (BuildConfig.DEBUG) {
+ Timber.tag("mixpanel").d("$eventName $properties")
+ }
mixpanel.track(eventName, properties)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fun track(eventName: String) { | |
| Timber.tag("mixpanel").d(eventName) | |
| mixpanel.track(eventName) | |
| } | |
| fun track(eventName: String, properties: JSONObject) { | |
| Timber.tag("mixpanel").d("$eventName $properties") | |
| mixpanel.track(eventName, properties) | |
| } | |
| import org.json.JSONObject | |
| import timber.log.Timber | |
| import com.spoony.spoony.BuildConfig | |
| // … other imports and class boilerplate … | |
| fun track(eventName: String) { | |
| if (BuildConfig.DEBUG) { | |
| Timber.tag("mixpanel").d(eventName) | |
| } | |
| mixpanel.track(eventName) | |
| } | |
| fun track(eventName: String, properties: JSONObject) { | |
| if (BuildConfig.DEBUG) { | |
| Timber.tag("mixpanel").d("$eventName $properties") | |
| } | |
| mixpanel.track(eventName, properties) | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/com/spoony/spoony/core/analytics/MixPanelTracker.kt around
lines 31 to 39, the current Timber.debug calls output full event names and
JSONObject properties (which can contain PII) to Logcat; wrap or remove these
logs so they run only in debug builds (e.g., guard with BuildConfig.DEBUG) and
avoid logging raw properties — log only non-sensitive metadata or the event name
(or a sanitized/hashed representation) inside the debug-only guard; apply this
change to both track(eventName: String) and track(eventName: String, properties:
JSONObject) overloads.
| onRegisterPost = { | ||
| viewModel.registerPost(it) | ||
|
|
||
| tracker.registerEvents.review2Completed( | ||
| reviewLength = state.detailReview.length, | ||
| photoCount = state.selectedPhotos.size, | ||
| hasDisappointment = state.optionalReview.isNotEmpty() | ||
| ) | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이벤트 타이밍 이슈: 등록 성공 전에 완료 이벤트가 로깅됩니다.
현재 review2Completed 이벤트가 registerPost 호출 직후 바로 로깅되고 있습니다. 하지만 registerPost는 비동기 작업이며, 실제 등록 성공 여부는 success 콜백이 호출될 때 확인됩니다.
등록이 실패하더라도 이벤트가 로깅되어 분석 데이터가 부정확해질 수 있습니다.
다음과 같이 수정하여 실제 성공 시점에 이벤트를 로깅하도록 개선하세요:
onRegisterPost = {
+ val onSuccessWithTracking = {
+ tracker.registerEvents.review2Completed(
+ reviewLength = state.detailReview.length,
+ photoCount = state.selectedPhotos.size,
+ hasDisappointment = state.optionalReview.isNotEmpty()
+ )
+ it()
+ }
- viewModel.registerPost(it)
-
- tracker.registerEvents.review2Completed(
- reviewLength = state.detailReview.length,
- photoCount = state.selectedPhotos.size,
- hasDisappointment = state.optionalReview.isNotEmpty()
- )
+ viewModel.registerPost(onSuccessWithTracking)
},🤖 Prompt for AI Agents
In
app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt
around lines 73 to 81, the review2Completed event is logged immediately after
calling viewModel.registerPost, but registerPost is asynchronous so the event
must only be logged on actual success; update the code to move the
tracker.registerEvents.review2Completed call into the registerPost success
callback (or observe the viewModel's success state/LiveData/Flow and call the
tracker when that indicates success), ensuring failure paths do not emit the
completion event and include the same review data when emitting on success.
| onEditComplete = { postId -> | ||
| onEditComplete(postId) | ||
| tracker.commonEvents.reviewEdited( | ||
| reviewTrackingModel = ReviewTrackingModel( | ||
| reviewId = postId, | ||
| authorUserId = state.userId, | ||
| placeName = state.selectedPlace.placeName, | ||
| category = state.selectedCategory.categoryName, | ||
| menuCount = state.menuList.size, | ||
| satisfactionScore = state.userSatisfactionValue.toDouble(), | ||
| reviewLength = state.detailReview.length, | ||
| photoCount = state.selectedPhotos.size, | ||
| hasDisappointment = state.optionalReview.isNotEmpty(), | ||
| savedCount = state.addMapCount | ||
| ) | ||
| ) | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이벤트 타이밍 이슈: 수정 성공 확인 전에 편집 이벤트가 로깅됩니다.
현재 reviewEdited 이벤트가 onEditComplete(postId) 호출 직후 바로 로깅되고 있습니다. 백엔드 수정 작업의 성공 여부를 확인하기 전에 이벤트가 기록되어, 실패한 수정도 성공으로 집계될 수 있습니다.
수정 작업의 성공을 확인한 후 이벤트를 로깅하도록 개선하거나, ViewModel에서 수정 성공 시점을 알 수 있도록 구조를 조정하는 것을 권장합니다. 예를 들어:
onEditComplete = { postId ->
// ViewModel에서 수정 성공 콜백을 제공하거나
// State로 수정 성공 여부를 관리하여
// 성공 확인 후 이벤트를 로깅하도록 수정
}또는 registerPost와 유사하게 success 콜백 패턴을 적용할 수 있습니다.
🤖 Prompt for AI Agents
In
app/src/main/java/com/spoony/spoony/presentation/register/RegisterEndScreen.kt
around lines 83-99, the reviewEdited event is logged immediately after calling
onEditComplete(postId) which means failures can be logged as successes; change
the flow so the event is only emitted after the edit is confirmed successful —
e.g., modify onEditComplete to accept a success callback or a result object (or
have ViewModel expose an editSuccess state) and move the
tracker.commonEvents.reviewEdited call into the success branch (only invoke when
the backend confirms the edit succeeded).
Related issue 🛠
Work Description ✏️
Screenshot 📸
N/A
Uncompleted Tasks 😅
To Reviewers 📢
드디어 숙제 하나 끝!!ㅠㅠ 하다보니 점점 욕심이 나서 캡슐화까지 해버렸어요...ㅎㅎ 그래도 막 엄청 딥하게 막 하지는 않았음!! 리뷰하실 때 구조정도만 봐주셔도 될 것 같아요! 그치만 같이 꼼꼼하게 확인해주신다면 개큰 박수와 사랑을 드리겠습니다😊
벌써부터 conflict가 너무 무서운데요..?? 세홍오빠 머지하고 나면 진짜 충돌 많이 생길 것 같아서 마음이 심란하네요ㅜㅜ 오빠 머지 언제할거야!!!!!!!! 이 pr을 보고있다면 당장 머지해
함수에 주석처리 되어있는 부분이 있는데요..!! 요건 현재 상태에서 넣을 수 없는 속성입니다..ㅎㅎ entryPoint의 경우 기획쌤들이랑 상의를 조금 해야 할 것 같구... 나머지 속성들은 최대한 작업 더 해서 넣으려구요 지금 주석처리 되어있는 부분 + 탐색뷰 필터 변경 이벤트만 제외하고 다 심었는데 위에서 말했던 것처럼 conflict가 너무 심할 것 같아서 하다가 중간에 멈췄어요ㅎㅎ 이건 세홍오빠 pr 머지하고 나면 한 번 더 작업해볼게요!!
로컬프로퍼티 수정하기
속성 못 넣은거 + 탐색뷰 필터 변경 이벤트 수정하고 draft 풀게요!! 구조는 수정되지 않을거라 리뷰 달아주셔도 괜찮습니다ㅎㅎSummary by CodeRabbit
New Features
Improvements
Chores