Skip to content

[Feature] 콜밴팟 알림 구현#1409

Open
kongwoojin wants to merge 11 commits intodevelopfrom
feature/1408-add-callvan-push-notification
Open

[Feature] 콜밴팟 알림 구현#1409
kongwoojin wants to merge 11 commits intodevelopfrom
feature/1408-add-callvan-push-notification

Conversation

@kongwoojin
Copy link
Copy Markdown
Member

PR 개요

PR 체크리스트

  • Code convention을 잘 지켰나요?
  • Lint check를 수행하였나요?
  • Assignees를 추가했나요?

작업사항

  • 버그 수정
  • 신규 기능
  • 코드 스타일 수정 (포맷팅 등)
  • 리팩토링 (기능 수정 X, API 수정 X)
  • 기타

작업사항의 상세한 설명

  • 콜밴팟 알림을 구현했습니다.
  • GroupChatActivitycreateIntent를 공통 buildIntent로 수정했습니다.

논의 사항

스크린샷

추가내용

  • develop, sprint 브랜치를 향하고 있습니다
  • production 브랜치를 향하고 있습니다

@kongwoojin kongwoojin self-assigned this Apr 9, 2026
@kongwoojin kongwoojin requested a review from a team as a code owner April 9, 2026 04:39
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 54157a4a-44a8-4faa-b54e-8463bde748cb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/1408-add-callvan-push-notification

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.

Base automatically changed from refactor/1405-refactor-implement-activity-deeplink to develop April 9, 2026 04:56

// Scheme url from backend is not matching with deeplink url in navigation
// So, let's navigate to correct deeplink from here
LaunchedEffect(navController) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] LaunchedEffect 키로 navController 사용은 의미론적으로 부적절합니다.

LaunchedEffect(navController)는 "navController가 변경될 때 다시 실행"이라는 의미이지만, 실제 의도는 "컴포지션 진입 시 단 한 번 실행"입니다. rememberNavController()는 리컴포지션에서 동일한 인스턴스를 반환하므로 현재는 기능적으로 동일하게 동작하지만, 코드 의도가 불명확합니다.

LaunchedEffect(Unit)을 사용하면 의도가 더 명확해집니다:

Suggested change
LaunchedEffect(navController) {
LaunchedEffect(Unit) {

추가로, targetId는 Activity intent에서 읽는 값이므로 변경되지 않습니다. Unit 키를 사용하는 것이 "딱 한 번 실행" 패턴에 부합합니다.

) {
navController.popBackStack()
}
LaunchedEffect(Unit) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Info] ✅ LaunchedEffect(Unit) 올바르게 사용하고 있습니다.

remember { } 안에서 Intent를 생성했던 기존 코드와 달리, Intent 생성 및 launcher.launch() 호출을 LaunchedEffect(Unit) 내부로 이동한 것은 올바른 패턴입니다. Unit 키를 사용해 리컴포지션과 무관하게 정확히 한 번만 실행되도록 보장하고 있으며, it.toRoute<CallvanNavType.CallvanChat>().postId 캡처도 NavBackStackEntry가 안정적이므로 안전합니다.

val targetChatId = intent.getIntExtra(EXTRA_CHAT_ROOM_ID, -1)
val targetClubId = intent.getIntExtra(EXTRA_CLUB_ID, -1)
val targetEventId = intent.getIntExtra(EXTRA_EVENT_ID, -1)
val targetPostId = intent.getIntExtra(EXTRA_POST_ID, -1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] EXTRA_POST_ID 미존재 시 -1GroupChatViewModel로 전파될 수 있습니다.

getIntExtra(EXTRA_POST_ID, -1)는 해당 Extra가 없을 때 -1을 반환합니다. 이 -1navigateToPair(EXTRA_POST_ID, -1)로 전달되면, GroupChatActivity가 실행될 경우 ViewModel에서 아래 코드가 실행됩니다:

val postId: Int? = savedStateHandle[EXTRA_POST_ID]  // -1 (null이 아님)
checkNotNull(postId)                                  // 통과됨
getCallvanPostDetail(-1)                              // 잘못된 API 호출

SchemeActivity.filter { it.second != null }로 null을 걸러내지만, SplashActivity에서는 이러한 필터링이 없어 -1이 그대로 전달됩니다.

타입 callvan-chat일 때만 GroupChatActivity가 실행되고, 해당 Deep Link URL에는 항상 postId가 포함되어야 하므로 실제 발생 가능성은 낮습니다. 그러나 방어적으로 처리하려면:

val targetPostId = if (intent.hasExtra(EXTRA_POST_ID)) intent.getIntExtra(EXTRA_POST_ID, -1) else -1

또는 기존 패턴과의 일관성을 위해 GroupChatViewModel에서 -1 검증을 추가하는 방법도 있습니다.

}

private fun getPostIdFromUrl(url: String): Int? {
return url.toUri().getQueryParameter("postId")?.toIntOrNull()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Trivial] 쿼리 파라미터 문자열 "postId"를 상수로 추출하는 것을 고려해볼 수 있습니다.

다른 메서드들도 동일한 패턴(하드코딩된 문자열)을 사용하고 있으므로 이는 기존 코드와 일관성이 있습니다. 다만 백엔드 API 파라미터 이름 변경 시 한 곳에서만 수정할 수 있도록 상수화하는 것을 추후 개선사항으로 고려해볼 수 있습니다.

현재 패턴과의 일관성 유지를 위해 이번 PR에서 수정하지 않아도 무방합니다.

@@ -35,7 +35,6 @@ interface Navigator {
): Intent

fun navigateToGroupChat(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] Navigator 인터페이스에서 postId 제거로 인해 암묵적 계약이 형성됩니다.

기존에는 navigateToGroupChat(context, postId)처럼 인터페이스만 봐도 어떤 데이터가 필요한지 명확했습니다. 변경 후에는 호출자가 반드시 putExtra(EXTRA_POST_ID, ...)를 별도로 호출해야 한다는 사실을 알아야 하며, 이를 누락하면 GroupChatViewModel.checkNotNull(postId)에서 크래시가 발생합니다.

이 설계가 buildIntent 유틸리티와의 일관성을 위한 의도적인 선택임은 이해하나, 추후 다른 진입점에서 GroupChatActivity를 실행할 때 EXTRA_POST_ID를 빠뜨리는 실수가 생길 수 있습니다.

현재 호출부가 Navigation.kt 하나뿐이라 큰 문제는 아니지만, 장기적으로는 파라미터를 인터페이스에 유지하거나 KDoc 주석으로 요구사항을 명시하는 것을 권장합니다.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

parameter 에 postId 가 있는 편이 좋을 거 같아요

CLUB("club", ClubActivity::class.java),

LOST_AND_FOUND("articles/lost-item", LostAndFoundActivity::class.java);
LOST_AND_FOUND("articles/lost-item", LostAndFoundActivity::class.java),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Info] ✅ SchemeType 열거형 구조가 깔끔합니다.

CALLVANCALLVAN_CHAT 항목 추가가 기존 패턴을 잘 따르고 있으며, ; 위치도 Kotlin 컨벤션에 맞게 마지막 항목 다음 줄로 이동된 점이 좋습니다. LOST_AND_FOUND에서 불필요하게 존재하던 빈 줄도 함께 정리한 점이 깔끔합니다.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 9, 2026

코드 리뷰 종합 의견

리뷰어: Senior Android Developer (10년차)


전체 평가

콜밴팟 알림 구현을 위한 Deep Link/Scheme 처리가 전반적으로 깔끔하게 구현되었습니다. Critical 또는 Major 수준의 버그는 없습니다. 기존 아키텍처 패턴을 잘 따르고 있으며 변경 범위도 적절합니다.


잘 된 점 ✅

항목 설명
LaunchedEffect(Unit) 사용 Navigation.kt에서 Intent 생성 및 launcher.launch()LaunchedEffect(Unit) 내부로 이동하여 리컴포지션 안전성을 올바르게 확보
url.toUri() 마이그레이션 SchemeActivity 전체에서 Uri.parse(url)url.toUri() (KTX 확장 함수)로 일관성 있게 교체
EXTRA_POST_ID 상수화 ExtraConstants.kt에 중앙화하여 ViewModel, NavigatorImpl, SchemeActivity, SplashActivity에서 동일 상수를 참조
createIntent 제거 + buildIntent 통일 GroupChatActivity의 static 팩토리 제거 후 공통 buildIntent 유틸리티로 통일
Detekt SpreadOperator 수정 SplashActivity*arrayOf(...) 제거로 CI 경고 해소

지적 사항 요약

중요도 파일 내용
Minor CallvanActivity.kt:45 LaunchedEffect(navController)LaunchedEffect(Unit) 권장 (인라인 코멘트 참조)
Minor SplashActivity.kt:248 EXTRA_POST_ID 부재 시 -1 전파 → GroupChatViewModel에서 잘못된 API 호출 가능성 (인라인 코멘트 참조)
Minor Navigator.kt:37 인터페이스에서 postId 제거로 인한 암묵적 계약 문제 (인라인 코멘트 참조)
Trivial SchemeActivity.kt:164 "postId" 하드코딩 (기존 패턴과 일관성 있으므로 필수 수정 아님)

추가 확인 권장 사항

GroupChatViewModel-1 처리:

// 현재 코드
val postId: Int? = savedStateHandle[EXTRA_POST_ID]
checkNotNull(postId)  // null은 걸러내지만 -1은 통과됨
getCallvanPostDetail(postId)

정상 플로우(알림 → SchemeActivity → GroupChatActivity)에서는 항상 유효한 postId가 전달되므로 실제 문제 발생 가능성은 낮습니다. 다만 방어적 코딩을 원한다면 -1 검증도 추가하는 것을 권장합니다:

val postId = checkNotNull(savedStateHandle.get<Int>(EXTRA_POST_ID)) { "postId must not be null" }
require(postId > 0) { "postId must be positive, got $postId" }

전반적으로 잘 구현된 PR입니다. Minor 사항들은 선택적으로 반영하셔도 무방하며, Trivial 항목은 다음 리팩토링 시 함께 처리하셔도 됩니다.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Comment on lines +45 to +48
LaunchedEffect(Unit) {
if (targetId == -1) return@LaunchedEffect
navController.navigate(CallvanNavType.CallvanDetail(targetId))
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] 화면 회전 시 중복 네비게이션 버그

LaunchedEffect(Unit)은 컴포지션이 새로 시작될 때마다 실행됩니다. rememberNavController()는 백스택 상태를 복원하지만, 화면 회전 후 재구성 시 LaunchedEffect(Unit) 블록이 다시 실행되어 이미 CallvanDetail이 스택에 있는 상태에서 또 navigate를 호출하게 됩니다. 결과적으로 백스택에 CallvanDetail이 중복으로 쌓입니다.

또한, 이 코드에는 더 우아한 기존 패턴이 존재합니다. EXTRA_NAVIGATE_TO_CREATEstartDestination으로 처리하는 반면, EXTRA_IDLaunchedEffect로 처리하는 것은 일관성이 없습니다.

권장 수정 방법:

Suggested change
LaunchedEffect(Unit) {
if (targetId == -1) return@LaunchedEffect
navController.navigate(CallvanNavType.CallvanDetail(targetId))
}
var hasNavigated by rememberSaveable { mutableStateOf(false) }
val targetId = intent.getIntExtra(EXTRA_ID, -1)
// Scheme url from backend is not matching with deeplink url in navigation
// Navigate to the correct destination from here instead of relying on URI matching.
LaunchedEffect(Unit) {
if (targetId == -1 || hasNavigated) return@LaunchedEffect
hasNavigated = true
navController.navigate(CallvanNavType.CallvanDetail(targetId))
}

또는 기존 startDestination 패턴을 따라 아예 startDestination에서 처리하는 방법도 고려해 보세요.

val targetId = intent.getIntExtra(EXTRA_ID, -1)

// Scheme url from backend is not matching with deeplink url in navigation
// So, let's navigate to correct deeplink from here
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Trivial] 프로덕션 코드의 비격식 표현

let's와 같은 비격식 표현은 프로덕션 코드 주석에 적합하지 않습니다.

Suggested change
// So, let's navigate to correct deeplink from here
// The scheme URL from the backend does not match the deep-link URL defined in the
// navigation graph, so navigate programmatically to the correct destination.

Comment on lines 91 to 95
LaunchedEffect(Unit) {
launcher.launch(intent)
navigator.navigateToGroupChat(context).apply {
putExtra(EXTRA_POST_ID, it.toRoute<CallvanNavType.CallvanChat>().postId)
}.let(launcher::launch)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] 화면 회전 시 GroupChatActivity 재실행 + 가독성 문제

두 가지 문제가 있습니다:

  1. 재실행 버그: LaunchedEffect(Unit)은 Activity 재생성(화면 회전) 시 재실행됩니다. 즉, launcher.launch(intent)가 다시 호출되어 GroupChatActivity가 또 실행됩니다.

  2. 암묵적 it 캡처: 바깥 composable<CallvanNavType.CallvanChat> { ... } 람다의 it(= NavBackStackEntry)을 LaunchedEffect 블록 안에서 암묵적으로 캡처하고 있어 가독성이 떨어집니다. 또한 .apply { ... }.let(launcher::launch) 체이닝은 필요 이상으로 복잡합니다.

권장 수정 방법:

Suggested change
LaunchedEffect(Unit) {
launcher.launch(intent)
navigator.navigateToGroupChat(context).apply {
putExtra(EXTRA_POST_ID, it.toRoute<CallvanNavType.CallvanChat>().postId)
}.let(launcher::launch)
}
composable<CallvanNavType.CallvanChat> { backStackEntry ->
val navigator = rememberNavigator()
val context = LocalContext.current
val postId = backStackEntry.toRoute<CallvanNavType.CallvanChat>().postId
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
navController.popBackStack()
}
var hasLaunched by rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
if (hasLaunched) return@LaunchedEffect
hasLaunched = true
val intent = navigator.navigateToGroupChat(context).apply {
putExtra(EXTRA_POST_ID, postId)
}
launcher.launch(intent)
}
}

Comment on lines +46 to 47
val postId: Int? = savedStateHandle[EXTRA_POST_ID]
checkNotNull(postId)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] CALLVAN_CHAT 스킴 URL에 postId 누락 시 앱 크래시

SchemeType.CALLVAN_CHAT으로 라우팅될 때, SchemeActivity는 URL에서 ?postId=X 쿼리 파라미터를 추출합니다. 만약 백엔드 알림 URL에 postId가 빠져 있거나 URL 형식이 변경된다면:

  1. getPostIdFromUrl()null 반환
  2. .filter { it.second != null }에 의해 필터링됨
  3. GroupChatActivityEXTRA_POST_ID 없이 실행됨
  4. savedStateHandle[EXTRA_POST_ID]null
  5. checkNotNull(postId)IllegalStateException 크래시

checkNotNull은 개발 단계 early-fail에 유용하지만, 프로덕션 환경에서는 사용자가 앱을 강제종료 당하는 경험을 합니다. 최소한 에러 상태를 UI에 표시하고 gracefully 종료하도록 처리해야 합니다.

Suggested change
val postId: Int? = savedStateHandle[EXTRA_POST_ID]
checkNotNull(postId)
val postId: Int? = savedStateHandle[EXTRA_POST_ID]
if (postId == null) {
postSideEffect(GroupChatSideEffect.FailedToLoadMessages)
return@container
}

혹은 checkNotNull 유지 시, 해당 케이스를 SchemeActivity에서 사전 검증하여 ID 없이는 GroupChatActivity를 실행하지 않도록 방어하는 로직을 추가하는 것을 권장합니다.

Comment on lines +24 to +25
CALLVAN_CHAT("callvan-chat", GroupChatActivity::class.java)
;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Trivial] 세미콜론 위치 스타일 불일치

기존 코드는 마지막 enum entry에 세미콜론을 같은 줄에 붙이는 스타일이었습니다 (LOST_AND_FOUND(...);). 세미콜론을 새 줄로 분리하면 스타일이 일관되지 않습니다.

Suggested change
CALLVAN_CHAT("callvan-chat", GroupChatActivity::class.java)
;
CALLVAN("callvan", CallvanActivity::class.java),
CALLVAN_CHAT("callvan-chat", GroupChatActivity::class.java);

Comment on lines 255 to 265
navigator.navigateTo(
context = this@SplashActivity,
type = Pair(EXTRA_TYPE, type),
*arrayOf(
Pair(EXTRA_ID, targetId),
Pair(EXTRA_BOARD_ID, targetBoardId),
Pair(EXTRA_ARTICLE_ID, targetArticleId),
Pair(EXTRA_CHAT_ROOM_ID, targetChatId),
Pair(EXTRA_CLUB_ID, targetClubId),
Pair(EXTRA_EVENT_ID, targetEventId)
)
Pair(EXTRA_ID, targetId),
Pair(EXTRA_BOARD_ID, targetBoardId),
Pair(EXTRA_ARTICLE_ID, targetArticleId),
Pair(EXTRA_CHAT_ROOM_ID, targetChatId),
Pair(EXTRA_CLUB_ID, targetClubId),
Pair(EXTRA_EVENT_ID, targetEventId),
Pair(EXTRA_POST_ID, targetPostId)
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Info] Spread operator 제거 - 좋은 개선입니다

*arrayOf(...) 스프레드 연산자를 제거하고 vararg 인자를 직접 전달하도록 변경한 것은 Detekt 경고 해결뿐 아니라 불필요한 배열 객체 생성을 막아 성능상으로도 좋은 개선입니다.

@github-actions github-actions bot requested a review from KYM-P April 9, 2026 10:16
@@ -42,7 +43,7 @@ class GroupChatViewModel @Inject constructor(
) : ViewModel(), ContainerHost<GroupChatState, GroupChatSideEffect> {

override val container = container<GroupChatState, GroupChatSideEffect>(GroupChatState()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Major] postId가 -1일 때 잘못된 API 호출 발생

이번 PR에서 CALLVAN_CHAT 스킴 타입이 새로 추가되어 GroupChatActivity가 푸시 알림을 통해서도 도달 가능해졌습니다. 이 경로에서 심각한 버그가 발생할 수 있습니다.

문제 흐름:

  1. 백엔드가 postId 파라미터 없이 callvan-chat 스킴 알림을 전송
  2. SplashActivity에서 intent.getIntExtra(EXTRA_POST_ID, -1)-1 반환
  3. -1GroupChatActivity의 Intent extra로 전달됨
  4. savedStateHandle[EXTRA_POST_ID]-1 반환 (null이 아님!)
  5. checkNotNull(-1) 통과 → getCallvanPostDetail(-1) 호출 → 잘못된 API 요청

SavedStateHandle에서 Int 타입의 키는 값이 -1이어도 null이 아니므로 checkNotNull이 통과됩니다.

Suggested change
override val container = container<GroupChatState, GroupChatSideEffect>(GroupChatState()) {
val postId: Int = checkNotNull(savedStateHandle.get<Int>(EXTRA_POST_ID)) {
"postId must not be null"
}
require(postId > 0) { "Invalid postId: $postId" }
getCallvanPostDetail(postId)

혹은 유효하지 않은 postId일 때 Activity를 종료하는 방어 로직이 필요합니다.

LaunchedEffect(Unit) {
launcher.launch(intent)
navigator.navigateToGroupChat(context).apply {
putExtra(EXTRA_POST_ID, it.toRoute<CallvanNavType.CallvanChat>().postId)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] apply {} 블록 내 it 의 의미가 불명확함

apply {} 블록 안에서 thisIntent 수신자를 가리키지만, it은 외부 composable<CallvanNavType.CallvanChat> { } 람다의 암시적 파라미터인 NavBackStackEntry를 캡처합니다. Kotlin 문법상 동작은 맞지만, itIntent인지 NavBackStackEntry인지 혼동을 일으킬 수 있어 유지보수 시 버그 위험이 있습니다.

Suggested change
putExtra(EXTRA_POST_ID, it.toRoute<CallvanNavType.CallvanChat>().postId)
LaunchedEffect(Unit) {
val postId = it.toRoute<CallvanNavType.CallvanChat>().postId
navigator.navigateToGroupChat(context)
.apply { putExtra(EXTRA_POST_ID, postId) }
.let(launcher::launch)
}

또는 외부 람다 파라미터를 명시적으로 이름 지정하는 것을 권장합니다:

composable<CallvanNavType.CallvanChat> { backStackEntry ->
    ...
    LaunchedEffect(Unit) {
        navigator.navigateToGroupChat(context).apply {
            putExtra(EXTRA_POST_ID, backStackEntry.toRoute<CallvanNavType.CallvanChat>().postId)
        }.let(launcher::launch)
    }
}

RebrandKoinTheme {
val navController = rememberNavController()

val targetId = intent.getIntExtra(EXTRA_ID, -1)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] remember 없이 recomposition마다 intent extra를 재읽음

targetIdsetContent {} 내부에서 remember 없이 읽히면, recomposition이 발생할 때마다 intent.getIntExtra()가 재호출됩니다. 이 값은 Activity 생명주기 동안 변하지 않으므로 remember로 캐싱하는 것이 Compose 관례에 맞습니다.

Suggested change
val targetId = intent.getIntExtra(EXTRA_ID, -1)
val targetId = remember { intent.getIntExtra(EXTRA_ID, -1) }


// Scheme url from backend is not matching with deeplink url in navigation
// So, let's navigate to correct deeplink from here
LaunchedEffect(Unit) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Minor] LaunchedEffect(Unit) 키 — onNewIntent 시 재탐색 불가

Unit을 키로 사용하면 Composition이 처음 진입할 때 단 한 번만 실행됩니다. 만약 이 Activity가 android:launchMode="singleTop" 또는 FLAG_ACTIVITY_SINGLE_TOP으로 재사용되어 onNewIntent()가 호출된다면, 새로운 targetId로 네비게이션이 트리거되지 않습니다.

현재 구조에서 onNewIntent를 처리하지 않는다면 이 패턴은 괜찮지만, 주석이나 TODO로 이 제약을 명시해두는 것을 권장합니다.

Suggested change
LaunchedEffect(Unit) {
// NOTE: LaunchedEffect(Unit) intentionally runs only once per composition entry.
// If this Activity needs to handle onNewIntent (e.g. singleTop), use
// rememberUpdatedState + LaunchedEffect(targetId) instead.
// TODO: Remove this workaround once backend deeplink scheme is aligned with nav graph.
LaunchedEffect(Unit) {

LOST_AND_FOUND("articles/lost-item", LostAndFoundActivity::class.java);
LOST_AND_FOUND("articles/lost-item", LostAndFoundActivity::class.java),
CALLVAN("callvan", CallvanActivity::class.java),
CALLVAN_CHAT("callvan-chat", GroupChatActivity::class.java)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Trivial] GroupChatActivity를 SchemeType에 직접 노출하는 구조 검토 필요

CALLVAN_CHAT("callvan-chat", GroupChatActivity::class.java) 항목은 GroupChatActivitycallvan 기능 외부(:koin 앱 모듈)에서 직접 참조됨을 의미합니다. GroupChatActivity는 callvan 채팅 전용 Activity임에도 불구하고 :feature:chat 모듈 내부에 있어, 모듈 경계가 약해질 수 있습니다.

중장기적으로는 CallvanActivity를 통해 내비게이션을 통합하거나(CALLVAN으로 진입 후 내부에서 라우팅), 또는 GroupChatActivity:feature:callvan 모듈로 이동하는 방안을 검토해볼 수 있습니다. 이번 PR의 직접적인 변경사항은 아니지만, 현재 코드의 CallvanActivity.kt 주석("Scheme url from backend is not matching")과 함께 아키텍처 관점에서 향후 개선 포인트로 남겨둘 만합니다.

Pair(EXTRA_CHAT_ROOM_ID, targetChatId),
Pair(EXTRA_CLUB_ID, targetClubId),
Pair(EXTRA_EVENT_ID, targetEventId),
Pair(EXTRA_POST_ID, targetPostId)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Info] SpreadOperator 제거 — 잘 처리했습니다!

*arrayOf(...) 대신 varargs를 직접 나열하는 방식으로 변경하여 Detekt의 SpreadOperator 경고를 제거한 것은 올바른 방향입니다. SpreadOperator는 내부적으로 배열 복사가 발생해 불필요한 메모리 할당이 생기므로, 이처럼 인수를 직접 나열하는 것이 성능상 더 좋습니다.

private fun getArticleIdFromUrl(url: String): Int? {
return Uri.parse(url).getQueryParameter("articleId")?.toIntOrNull()
return url.toUri().getQueryParameter("articleId")?.toIntOrNull()
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[Info] Uri.parse()url.toUri() 일괄 마이그레이션 — 잘 처리했습니다!

androidx.core.net.toUri() KTX 확장함수로의 일괄 전환은 Kotlin 관용적 스타일에 부합합니다. 기능상 동일하지만 코드의 일관성이 향상되었습니다.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants