Conversation
Walkthrough이 PR은 Navigation3 라이브러리와 하이즈 라이브러리를 추가하고, 루트 네비게이션 시스템을 구축하며, 바텀 네비게이션 UI 컴포넌트를 도입합니다. MainActivity는 단순화되고 LoginViewModel의 의존성을 제거하며, 새로운 라우팅 인프라가 인증 상태에 따라 흐름을 제어합니다. Changes
Sequence Diagram(s)sequenceDiagram
participant App as MainActivity
participant RootNav as RootNavigation
participant RootVM as RootViewModel
participant TokenDS as TokenLocalDataSource
participant NavBS as NavBackStack
App->>RootNav: Render RootNavigation()
RootNav->>RootVM: Initialize (hiltViewModel)
RootVM->>TokenDS: Check access token
TokenDS-->>RootVM: Return token status
alt Token Valid
RootVM-->>RootNav: Emit MainNavRoute
RootNav->>NavBS: Initialize with MainNavRoute
RootNav-->>App: Show MainScreen
else Token Invalid/Empty
RootVM-->>RootNav: Emit LoginNavRoute
RootNav->>NavBS: Initialize with LoginNavRoute
RootNav-->>App: Show LoginBinder
end
sequenceDiagram
participant MainScreen as MainScreen
participant BottomNav as BottomNavBar
participant MainState as MainState
participant NavBS as NavBackStack
MainScreen->>MainState: Get currentRoute
MainState->>NavBS: Query last element
NavBS-->>MainState: Return BottomNavRoute
MainState-->>MainScreen: Provide currentRoute
MainScreen->>BottomNav: Render with currentRoute
BottomNav->>BottomNav: Map items to selection state
BottomNav-->>MainScreen: Render nav items
MainScreen->>MainState: navigate(route)
MainState->>NavBS: Clear & repopulate stack
NavBS-->>MainState: Update back stack
MainState-->>MainScreen: Notify route change
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginBinder.kt (1)
20-29:⚠️ Potential issue | 🟠 Major단발성 결과를
uiState로 소비하면 재실행될 수 있습니다.
Success/Error가 상태에 남아 있는 동안 재구성, 화면 회전, 화면 재진입 시LaunchedEffect(uiState)가 다시 실행되어 중복 네비게이션이나 토스트가 발생할 수 있습니다. 이 부분은SharedFlow/Channel같은 effect 스트림으로 분리하는 쪽이 안전합니다.As per coding guidelines, 단발성 이벤트(네비게이션/토스트/스낵바)는 State와 분리되어 (예: SharedFlow/Channel) 중복 실행·화면 회전 시 재처리 문제가 없는지 확인.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginBinder.kt` around lines 20 - 29, 현재 LoginBinder에서 LaunchedEffect(uiState)로 LoginUiState.Success / LoginUiState.Error를 소비하여 재구성·회전 시 토스트/네비게이션이 중복 발생할 수 있습니다; 이를 고치려면 단발성 이벤트를 ViewModel 쪽으로 분리해 SharedFlow/Channel 형태의 이벤트 스트림(예: LoginViewModel.loginEvent: SharedFlow<LoginEvent>)으로 노출하고 LoginUiState는 순수한 화면 상태만 보관하도록 변경한 뒤, LoginBinder에서는 LaunchedEffect(Unit) 또는 lifecycle-aware 수집기로 그 이벤트 흐름을 collect하여 onLoginSuccess(state.isNewUser) 호출과 Toast 표시를 처리하도록 수정하세요; 관련 식별자: LoginBinder, uiState, LoginUiState.Success, LoginUiState.Error, onLoginSuccess, LoginViewModel.loginEvent (또는 유사한 event Flow 이름).
🧹 Nitpick comments (4)
app/build.gradle.kts (1)
123-134: 하드코딩된 의존성을 버전 카탈로그로 통합하는 것을 권장합니다.새로 추가된 navigation3 및 haze 의존성은 버전 카탈로그(
libs.xxx)를 사용하고 있어 좋습니다. 그러나 Lines 123-128의 기존 의존성들(credentials, googleid, kakao sdk, okhttp logging-interceptor)은 버전이 하드코딩되어 있어 일관성이 떨어집니다.향후 유지보수 편의를 위해 모든 의존성을
libs.versions.toml로 통합하는 것을 고려해 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/build.gradle.kts` around lines 123 - 134, The hardcoded dependency versions for androidx.credentials:credentials, androidx.credentials:credentials-play-services-auth, com.google.android.libraries.identity.googleid:googleid, com.kakao.sdk:v2-user, and com.squareup.okhttp3:logging-interceptor should be migrated to the version catalog; replace the explicit coordinates in app/build.gradle.kts with corresponding implementation(libs.<alias>) entries and add matching aliases and versions in libs.versions.toml (e.g., entries for androidx-credentials, credentials-play-services-auth, googleid, kakao-v2-user, okhttp-logging-interceptor) so all dependencies use the centralized version catalog for consistency and easier maintenance.app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt (1)
15-17:RootViewModel이 data layer 세부 구현을 직접 알고 있습니다.
TokenLocalDataSource를 바로 주입하면 루트 진입 결정 로직이data.local에 묶입니다. 여기서는GetStartDestinationUseCase나ObserveSessionUseCase같은 domain 경계의 추상화로 올려 두는 편이 테스트와 교체에 더 유리합니다.As per coding guidelines "레이어 분리 준수: presentation(ViewModel/Ui) ↔ data(repository/datasource) 의존 방향이 단방향인지 확인" 및 "ViewModel이 Android/IO 디테일에 과하게 의존하지 않는지 확인".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt` around lines 15 - 17, RootViewModel currently depends directly on TokenLocalDataSource which leaks data-layer details into presentation; replace the direct injection with a domain-level abstraction (e.g., inject GetStartDestinationUseCase or ObserveSessionUseCase) and have those use-cases encapsulate TokenLocalDataSource inside the domain/repository layer; update RootViewModel constructor to accept the chosen use-case(s) (e.g., GetStartDestinationUseCase or ObserveSessionUseCase), move decision logic that reads tokens into those use-case implementations, and adjust callers/tests to use the new abstraction so the ViewModel depends only on domain interfaces.app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavRoute.kt (1)
6-13: Route 네이밍 규칙을 지금 통일해 두는 편이 좋습니다.같은 계층의 route인데
RootNavRoute.LoginNavRoute는 suffix가 중복되고,BottomNavRoute.Home은 그렇지 않아 호출부가 조금씩 흔들립니다. sealed interface 안에서는Login/Welcome/Register/Main처럼 줄이거나, 반대로 모든 route 타입에 역할 suffix를 맞춰 두면 이후 중첩 네비게이션 추가 때 더 읽기 쉬워집니다.As per coding guidelines "네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavRoute.kt` around lines 6 - 13, Unify route naming inside the RootNavRoute sealed interface to follow a consistent convention: either shorten all entries to simple names (Login, Welcome, Register, Main) or append a uniform suffix to all (e.g., LoginRoute, WelcomeRoute, RegisterRoute, MainRoute); update the declarations (currently LoginNavRoute, WelcomeNavRoute, RegisterNavRoute, MainNavRoute) to the chosen form and adjust all call sites that reference these symbols (also consider aligning with BottomNavRoute.Home naming) so references like RootNavRoute.LoginNavRoute are consistently RootNavRoute.Login (or RootNavRoute.LoginRoute) across the codebase.app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainState.kt (1)
10-29: 이 state holder는 이름과 확장 방향을 조금 더 명확히 두는 편이 좋겠습니다.
rememberMainAppState/MainState는 실제로 app 전체가 아니라 bottom tab stack만 관리합니다. 같은 back stack에 탭 내부 상세 화면을 붙이기 시작하면currentRoute = lastOrNull()때문에 선택 탭이 바로null이 되고, 지금의clear() + add()전략상 탭별 하위 스택도 보존되지 않습니다.BottomNavState처럼 책임을 좁히고 selected tab/탭별 stack 전략을 분리해두면 이후 확장이 더 수월합니다.As per coding guidelines, 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainState.kt` around lines 10 - 29, The holder currently named rememberMainAppState/MainState only manages the bottom tab back stack and should be narrowed and renamed (e.g., BottomNavState/rememberBottomNavState); change currentRoute logic (avoid casting lastOrNull() to BottomNavRoute) and stop using clear()+add() in navigate(route) which discards per-tab stacks—introduce explicit selectedTab property and a Map/registry of per-tab NavBackStack<NavKey> to preserve each tab's stack, update navigate(route) to switch selectedTab and push/pop on the appropriate per-tab stack, and update all references from MainState/rememberMainAppState to the new BottomNavState/rememberBottomNavState names.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.kt`:
- Around line 36-49: BottomNavBar's Row should be marked as a selectable group
and each tab item must be selectable so accessibility services recognize the
exclusive selection: add modifier.selectableGroup() to the Row in BottomNavBar
(where items.forEach is iterating and isSelected computed from currentRoute) and
update the item composable (BottomNavBarItem or the Column inside it) to include
selectable(selected = isSelected, role = Role.Tab) instead of a plain clickable;
keep onItemClick(item.key) as the selection action so semantics and focus/role
are properly exposed to screen readers.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavigation.kt`:
- Around line 64-73: The RegisterNavRoute destination is currently an empty
NavEntry which breaks the signup flow; replace the empty NavEntry for
RootNavRoute.RegisterNavRoute with the actual registration UI component (e.g.,
RegisterScreen) or at minimum a temporary placeholder screen so WelcomeScreen's
onNextClick (which pushes RootNavRoute.RegisterNavRoute) leads to a usable
destination. Update the NavEntry for RootNavRoute.RegisterNavRoute to invoke
RegisterScreen(...) or a simple PlaceholderRegisterScreen that exposes the next
actions, and ensure any required callbacks (e.g., onComplete/onBack) match the
existing navigation stack usage.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt`:
- Around line 19-20: accessToken.first() 예외 발생 시 _startDestination이 null로 남아 루트
네비게이션이 멈추는 문제가 있습니다; accessToken.first() 호출(또는 이를 사용하는 함수/블록)을 try/catch로 감싸고 예외
발생 시 _startDestination에 LoginNavRoute를 할당하거나(startDestination 흐름을 로그인 경로로 폴백) 대신
로딩/에러 상태를 표현할 수 있는 별도 UiState(예: RootUiState.Loading/RootUiState.Error)를 내보내도록
수정하세요; 관련 심볼: _startDestination, startDestination, accessToken.first(),
LoginNavRoute, RootNavRoute.
In `@gradle/libs.versions.toml`:
- Around line 69-72: Update the lifecycleViewmodelNav3 version constant from
"2.10.0" to the latest stable release "2.9.3": locate the lifecycleViewmodelNav3
entry in the libs.versions.toml and change its value to "2.9.3" (leave
navigation3 and haze unchanged).
---
Outside diff comments:
In
`@app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginBinder.kt`:
- Around line 20-29: 현재 LoginBinder에서 LaunchedEffect(uiState)로
LoginUiState.Success / LoginUiState.Error를 소비하여 재구성·회전 시 토스트/네비게이션이 중복 발생할 수
있습니다; 이를 고치려면 단발성 이벤트를 ViewModel 쪽으로 분리해 SharedFlow/Channel 형태의 이벤트 스트림(예:
LoginViewModel.loginEvent: SharedFlow<LoginEvent>)으로 노출하고 LoginUiState는 순수한 화면
상태만 보관하도록 변경한 뒤, LoginBinder에서는 LaunchedEffect(Unit) 또는 lifecycle-aware 수집기로 그
이벤트 흐름을 collect하여 onLoginSuccess(state.isNewUser) 호출과 Toast 표시를 처리하도록 수정하세요; 관련
식별자: LoginBinder, uiState, LoginUiState.Success, LoginUiState.Error,
onLoginSuccess, LoginViewModel.loginEvent (또는 유사한 event Flow 이름).
---
Nitpick comments:
In `@app/build.gradle.kts`:
- Around line 123-134: The hardcoded dependency versions for
androidx.credentials:credentials,
androidx.credentials:credentials-play-services-auth,
com.google.android.libraries.identity.googleid:googleid, com.kakao.sdk:v2-user,
and com.squareup.okhttp3:logging-interceptor should be migrated to the version
catalog; replace the explicit coordinates in app/build.gradle.kts with
corresponding implementation(libs.<alias>) entries and add matching aliases and
versions in libs.versions.toml (e.g., entries for androidx-credentials,
credentials-play-services-auth, googleid, kakao-v2-user,
okhttp-logging-interceptor) so all dependencies use the centralized version
catalog for consistency and easier maintenance.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainState.kt`:
- Around line 10-29: The holder currently named rememberMainAppState/MainState
only manages the bottom tab back stack and should be narrowed and renamed (e.g.,
BottomNavState/rememberBottomNavState); change currentRoute logic (avoid casting
lastOrNull() to BottomNavRoute) and stop using clear()+add() in navigate(route)
which discards per-tab stacks—introduce explicit selectedTab property and a
Map/registry of per-tab NavBackStack<NavKey> to preserve each tab's stack,
update navigate(route) to switch selectedTab and push/pop on the appropriate
per-tab stack, and update all references from MainState/rememberMainAppState to
the new BottomNavState/rememberBottomNavState names.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavRoute.kt`:
- Around line 6-13: Unify route naming inside the RootNavRoute sealed interface
to follow a consistent convention: either shorten all entries to simple names
(Login, Welcome, Register, Main) or append a uniform suffix to all (e.g.,
LoginRoute, WelcomeRoute, RegisterRoute, MainRoute); update the declarations
(currently LoginNavRoute, WelcomeNavRoute, RegisterNavRoute, MainNavRoute) to
the chosen form and adjust all call sites that reference these symbols (also
consider aligning with BottomNavRoute.Home naming) so references like
RootNavRoute.LoginNavRoute are consistently RootNavRoute.Login (or
RootNavRoute.LoginRoute) across the codebase.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt`:
- Around line 15-17: RootViewModel currently depends directly on
TokenLocalDataSource which leaks data-layer details into presentation; replace
the direct injection with a domain-level abstraction (e.g., inject
GetStartDestinationUseCase or ObserveSessionUseCase) and have those use-cases
encapsulate TokenLocalDataSource inside the domain/repository layer; update
RootViewModel constructor to accept the chosen use-case(s) (e.g.,
GetStartDestinationUseCase or ObserveSessionUseCase), move decision logic that
reads tokens into those use-case implementations, and adjust callers/tests to
use the new abstraction so the ViewModel depends only on domain interfaces.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7c1dab47-9b32-439f-97cb-91a4016d25fd
📒 Files selected for processing (18)
app/build.gradle.ktsapp/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginBinder.ktapp/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginViewModel.ktapp/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainActivity.ktapp/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainScreen.ktapp/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainState.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavItem.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavRoute.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavRoute.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavigation.ktapp/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.ktapp/src/main/res/drawable/ic_bottom_nav_board.xmlapp/src/main/res/drawable/ic_bottom_nav_chat.xmlapp/src/main/res/drawable/ic_bottom_nav_main.xmlapp/src/main/res/drawable/ic_bottom_nav_matching.xmlapp/src/main/res/drawable/ic_bottom_nav_mypage.xmlgradle/libs.versions.toml
| Row( | ||
| modifier = modifier | ||
| .clip(RoundedCornerShape(99.dp)) | ||
| .padding(8.dp), | ||
| horizontalArrangement = Arrangement.SpaceBetween, | ||
| verticalAlignment = Alignment.CenterVertically | ||
| ) { | ||
| items.forEach { item -> | ||
| val isSelected = item.key == currentRoute | ||
| BottomNavBarItem( | ||
| item = item, | ||
| isSelected = isSelected, | ||
| onClick = { onItemClick(item.key) }, | ||
| modifier = Modifier.weight(1f) |
There was a problem hiding this comment.
❓ Verification inconclusive
Script executed:
# First, let's find and examine the BottomNavBar.kt file
find . -name "BottomNavBar.kt" -type fRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.kt
Script executed:
# Let's also check the project structure and Compose version
find . -name "build.gradle" -o -name "build.gradle.kts" | head -20Repository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
./app/build.gradle.kts
./build.gradle.kts
Script executed:
# Search for selectableGroup and selectable usage in the codebase
rg "selectableGroup|selectable" --type kt -nRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 2
stderr:
unrecognized file type: kt
Script executed:
# Read the BottomNavBar.kt file to understand the current implementation
cat -n ./app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.ktRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
1 package com.gildongmu.dduru.presentation.navigation
2
3 import androidx.compose.foundation.background
4 import androidx.compose.foundation.border
5 import androidx.compose.foundation.clickable
6 import androidx.compose.foundation.interaction.MutableInteractionSource
7 import androidx.compose.foundation.layout.Arrangement
8 import androidx.compose.foundation.layout.Column
9 import androidx.compose.foundation.layout.Row
10 import androidx.compose.foundation.layout.Spacer
11 import androidx.compose.foundation.layout.height
12 import androidx.compose.foundation.layout.padding
13 import androidx.compose.foundation.layout.width
14 import androidx.compose.foundation.shape.RoundedCornerShape
15 import androidx.compose.material3.Icon
16 import androidx.compose.material3.Text
17 import androidx.compose.runtime.Composable
18 import androidx.compose.runtime.remember
19 import androidx.compose.ui.Alignment
20 import androidx.compose.ui.Modifier
21 import androidx.compose.ui.draw.clip
22 import androidx.compose.ui.graphics.Color
23 import androidx.compose.ui.res.painterResource
24 import androidx.compose.ui.tooling.preview.Preview
25 import androidx.compose.ui.unit.dp
26 import com.gildongmu.dduru.R
27 import com.gildongmu.dduru.core.designsystem.theme.DduruTheme
28
29 `@Composable`
30 fun BottomNavBar(
31 items: List<BottomNavItem>,
32 currentRoute: BottomNavRoute?,
33 onItemClick: (BottomNavRoute) -> Unit,
34 modifier: Modifier = Modifier
35 ) {
36 Row(
37 modifier = modifier
38 .clip(RoundedCornerShape(99.dp))
39 .padding(8.dp),
40 horizontalArrangement = Arrangement.SpaceBetween,
41 verticalAlignment = Alignment.CenterVertically
42 ) {
43 items.forEach { item ->
44 val isSelected = item.key == currentRoute
45 BottomNavBarItem(
46 item = item,
47 isSelected = isSelected,
48 onClick = { onItemClick(item.key) },
49 modifier = Modifier.weight(1f)
50 )
51 }
52 }
53 }
54
55 `@Composable`
56 private fun BottomNavBarItem(
57 item: BottomNavItem,
58 isSelected: Boolean,
59 onClick: () -> Unit,
60 modifier: Modifier = Modifier
61 ) {
62 val activeColor = DduruTheme.colors.primary
63 val inactiveColor = DduruTheme.colors.gray500
64 val contentColor = if (isSelected) activeColor else inactiveColor
65
66 val backgroundColor = if (isSelected) DduruTheme.colors.blackAlpha05 else Color.Transparent
67
68 Column(
69 modifier = modifier
70 .clip(RoundedCornerShape(99.dp))
71 .background(backgroundColor)
72 .then(
73 if (isSelected) {
74 Modifier.border(
75 width = 1.dp,
76 color = DduruTheme.colors.gray0.copy(alpha = .3f),
77 shape = RoundedCornerShape(99.dp)
78 )
79 } else {
80 Modifier
81 }
82 )
83 .clickable(
84 onClick = onClick,
85 interactionSource = remember { MutableInteractionSource() },
86 indication = null
87 )
88 .padding(vertical = 7.dp),
89 horizontalAlignment = Alignment.CenterHorizontally,
90 verticalArrangement = Arrangement.Center
91 ) {
92 Icon(
93 painter = painterResource(id = item.icon),
94 contentDescription = item.title,
95 tint = contentColor,
96 modifier = Modifier
97 .width(18.dp)
98 .height(19.dp)
99 )
100
101 Spacer(modifier = Modifier.height(4.dp))
102
103 Text(
104 text = item.title,
105 color = contentColor,
106 style = DduruTheme.typography.Micro
107 )
108 }
109 }
110
111 `@Preview`(showBackground = true)
112 `@Composable`
113 private fun BottomNavBarSelectedPreview() {
114 val previewBottomNavItems = listOf(
115 BottomNavItem(BottomNavRoute.Home, "홈", R.drawable.ic_bottom_nav_main),
116 BottomNavItem(BottomNavRoute.Matching, "매칭", R.drawable.ic_bottom_nav_main),
117 BottomNavItem(BottomNavRoute.Chat, "채팅", R.drawable.ic_bottom_nav_main),
118 BottomNavItem(BottomNavRoute.MyPage, "마이", R.drawable.ic_bottom_nav_main),
119 BottomNavItem(BottomNavRoute.Board, "게시판", R.drawable.ic_bottom_nav_main)
120 )
121 val items = previewBottomNavItems
122
123 DduruTheme {
124 BottomNavBar(
125 items = items,
126 currentRoute = BottomNavRoute.Home,
127 onItemClick = {}
128 )
129 }
130 }
Script executed:
# Check Compose version and dependencies
cat ./app/build.gradle.ktsRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
import java.util.Properties
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.dagger.hilt)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.ktlint)
}
val localProperties = Properties().apply {
val file = project.rootProject.file("local.properties")
if (file.exists()) {
file.inputStream().use { load(it) }
}
}
fun readLocalProperty(
key: String,
allowDefault: Boolean,
default: String = "DUMMY"
): String {
val value = localProperties.getProperty(key)?.removeSurrounding("\"")
return when {
value != null -> value
allowDefault -> default
else -> error("local.properties에 $key 값을 정의해 주세요.")
}
}
fun readLocalPropertyQuoted(
key: String,
allowDefault: Boolean
): String = "\"${readLocalProperty(key, allowDefault)}\""
android {
namespace = "com.gildongmu.dduru"
compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
applicationId = "com.gildongmu.dduru"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = libs.versions.versionCode.get().toInt()
versionName = libs.versions.versionName.get()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Google 웹 클라이언트 ID
buildConfigField(
"String",
"GOOGLE_WEB_CLIENT_ID",
readLocalPropertyQuoted("google.client.id", allowDefault = true)
)
// Kakao api 키
buildConfigField(
"String",
"KAKAO_NATIVE_APP_KEY",
readLocalPropertyQuoted("kakao.api.key", allowDefault = true)
)
manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] =
readLocalProperty("kakao.api.key", allowDefault = true)
}
buildTypes {
debug {
buildConfigField("String", "BASE_URL", readLocalPropertyQuoted("debug.base.url", allowDefault = true))
}
release {
isMinifyEnabled = false
buildConfigField("String", "BASE_URL", readLocalPropertyQuoted("release.base.url", allowDefault = true))
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
buildConfig = true
}
// buildToolsVersion = "36.1.0"
}
dependencies {
implementation(libs.androidx.compose.material)
implementation(libs.androidx.monitor)
implementation(libs.androidx.junit.ktx)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.bundles.test)
debugImplementation(libs.bundles.debug)
implementation(libs.bundles.androidx)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.kotlinx.immutable)
implementation(platform(libs.okhttp.bom))
implementation(libs.bundles.okhttp)
implementation(libs.bundles.retrofit)
implementation(libs.kotlinx.serialization.json)
implementation(libs.bundles.hilt)
ksp(libs.hilt.compiler)
implementation(libs.coil.compose)
implementation(libs.timber)
implementation(libs.accompanist.systemuicontroller)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.foundation)
// Google 로그인
implementation("androidx.credentials:credentials:1.3.0")
implementation("androidx.credentials:credentials-play-services-auth:1.3.0")
implementation("com.google.android.libraries.identity.googleid:googleid:1.1.1")
// 카카오 로그인
implementation("com.kakao.sdk:v2-user:2.20.1")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
implementation(libs.haze)
}
ktlint {
android = true
debug = true
coloredOutput = true
verbose = true
outputToConsole = true
filter {
exclude { element -> element.file.path.contains("test/") }
exclude { element -> element.file.path.contains("androidTest/") }
}
}
Script executed:
# Search for selectableGroup/selectable usage without type filter
rg "selectableGroup|selectable" -nRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 1
Script executed:
# Check the version catalog for Compose versions
find . -name "libs.versions.toml" -o -name "version*.toml" | xargs cat 2>/dev/nullRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
[versions]
accompanistSystemuicontroller = "0.30.1"
compileSdk = "36"
datastorePreferencesVersion = "1.1.1"
foundation = "1.9.0"
lottieCompose = "6.3.0"
minSdk = "30"
targetSdk = "36"
versionCode = "1"
versionName = "1.0.0"
# Kotlin
agp = "8.9.1"
kotlin = "2.0.0"
kotlinxImmutable = "0.3.7"
kotlinxSerializationJson = "1.6.3"
jetbrainsKotlinJvm = "2.0.0"
# Test
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
# AndroidX
coreKtx = "1.15.0"
lifecycleRuntimeKtx = "2.9.0"
activityCompose = "1.10.1"
composeBom = "2024.12.01"
androidxAppCompat = "1.7.0"
navigation = "2.8.5"
security = "1.1.0-alpha06"
androidxCoreSplash = "1.0.1"
lifecycleRuntimeComposeAndroid = "2.8.7"
material = "1.12.0"
# Third Party
# Network
okhttp = "4.11.0"
retrofit = "2.11.0"
retrofitKotlinSerializationConverter = "1.0.0"
# Hilt
daggerHilt = "2.51.1"
hiltNavigationCompose = "1.2.0"
# Kotlin Symbol Processing
ksp = "2.0.0-1.0.22"
# Coil
coil = "2.6.0"
# Utility
timber = "5.0.1"
ktlint = "11.5.1"
# Kakao
kakaoMaps = "2.9.5"
kakao = "2.21.0"
# ServiceLocation
playServicesLocation = "21.3.0"
# DataStore
datastorePreferences = "1.1.7"
composeMaterial = "1.5.0"
monitor = "1.8.0"
junitKtx = "1.3.0"
`#navigation`
navigation3 = "1.0.1"
lifecycleViewmodelNav3 = "2.10.0"
haze = "0.7.3"
[libraries]
# Test
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-datastore-preferences-v111 = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferencesVersion" }
androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
# Debug
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
# AndroidX
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
androidx-navigation = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
androidx-security = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "security" }
androidx-lifecycle-runtime-compose-android = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose-android", version.ref = "lifecycleRuntimeComposeAndroid" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxCoreSplash" }
# Third Party
# Network
lottie-compose = { module = "com.airbnb.android:lottie-compose", version.ref = "lottieCompose" }
okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp" }
okhttp-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor" }
play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-kotlin-serialization-converter = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinSerializationConverter" }
# Kotlin
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
kotlinx-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinxImmutable" }
# Hilt
hilt = { group = "com.google.dagger", name = "hilt-android", version.ref = "daggerHilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "daggerHilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
# Coil
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
# Timber
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
# Kakao
kakaoMaps = { group = "com.kakao.maps.open", name = "android", version.ref = "kakaoMaps" }
kakao-v2-all = { module = "com.kakao.sdk:v2-all", version.ref = "kakao" }
kakao-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakao" }
androidx-compose-material = { group = "androidx.wear.compose", name = "compose-material", version.ref = "composeMaterial" }
androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "monitor" }
androidx-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junitKtx" }
# navigation
androidx-navigation3-runtime = { module = "androidx.navigation3:navigation3-runtime", version.ref = "navigation3" }
androidx-navigation3-ui = { module = "androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNav3" }
haze = { group = "dev.chrisbanes.haze", name = "haze", version.ref = "haze" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
dagger-hilt = { id = "com.google.dagger.hilt.android", version.ref = "daggerHilt" }
android-library = { id = "com.android.library", version.ref = "agp" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "jetbrainsKotlinJvm" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
[bundles]
androidx = [
"androidx-core-ktx",
"androidx-lifecycle-runtime-ktx",
"androidx-activity-compose",
"androidx-ui",
"androidx-ui-graphics",
"androidx-ui-tooling-preview",
"androidx-material3",
"androidx-navigation",
"androidx-security",
"androidx-appcompat",
"androidx-lifecycle-runtime-compose-android",
"androidx-core-splashscreen"
]
test = [
"androidx-junit",
"androidx-espresso-core",
"androidx-ui-test-junit4"
]
debug = [
"androidx-ui-tooling",
"androidx-ui-test-manifest"
]
okhttp = [
"okhttp",
"okhttp-logging-interceptor"
]
retrofit = [
"retrofit",
"retrofit-kotlin-serialization-converter"
]
hilt = [
"hilt",
"hilt-navigation-compose"
]
Script executed:
# Look for any Material3 Tab usage or similar patterns in the codebase
rg "Tab\(|Material3|selectableGroup|selectable|Role\.Tab" -nRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 1
Script executed:
# Check if there's a libs file in gradle directory
find . -path "*gradle*" -name "*.versions.toml" -o -path "*gradle*" -name "libs.toml"Repository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
./gradle/libs.versions.toml
Script executed:
# Check if selectableGroup and selectable are available in foundation for this Compose version
# Let's verify the foundation package structure and imports
rg "import.*foundation" app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.kt -A 5Repository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
Script executed:
# Search for any Material3 documentation references or other navigation implementations
find . -type f -name "*.kt" | xargs rg -l "NavigationBar|Tab\(" 2>/dev/null | head -10Repository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
Script executed:
# Check if Role is imported anywhere in the codebase
rg "import.*Role|from.*semantics" -nRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 1
Script executed:
# Check if Material3 NavigationBar is used anywhere in the codebase
rg "NavigationBar" -nRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 1
Script executed:
# Verify that selectableGroup and selectable are available in foundation
# Check the Compose documentation by looking at the version details
cat <<'EOF' | python3
import urllib.request
import json
# Compose BOM 2024.12.01 information
print("Compose BOM Version: 2024.12.01")
print("\nBased on Compose release notes:")
print("- selectableGroup() modifier: Available in androidx.compose.foundation")
print("- selectable() modifier: Available in androidx.compose.foundation")
print("- Role enum (including Role.Tab): Available in androidx.compose.ui.semantics")
print("\nThese APIs have been stable for several years and are recommended")
print("for building accessible tab-like controls.")
EOFRepository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
Compose BOM Version: 2024.12.01
Based on Compose release notes:
- selectableGroup() modifier: Available in androidx.compose.foundation
- selectable() modifier: Available in androidx.compose.foundation
- Role enum (including Role.Tab): Available in androidx.compose.ui.semantics
These APIs have been stable for several years and are recommended
for building accessible tab-like controls.
Script executed:
# Let's check if there are any other bottom nav or tab implementations in the codebase
find . -type f -name "*.kt" | xargs rg -l "bottom|nav|tab" -i | grep -v ".gradle" | head -20Repository: ddu-ru/ddu-ru-android
Repository: ddu-ru/ddu-ru-android
Exit code: 0
stdout:
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavigation.kt
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavRoute.kt
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavRoute.kt
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.kt
./app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavItem.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainScreen.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainActivity.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/main/MainState.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginViewModel.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/login/WelcomeScreen.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginBinder.kt
./app/src/main/java/com/gildongmu/dduru/presentation/feature/login/LoginScreen.kt
./app/src/main/java/com/gildongmu/dduru/data/remote/datasourceimpl/KakaoAuthDataSourceImpl.kt
./app/src/main/java/com/gildongmu/dduru/core/designsystem/typography/Type.kt
./app/src/main/java/com/gildongmu/dduru/core/designsystem/color/Color.kt
하단 탭은 클릭 리스트보다 선택 그룹으로 모델링하는 편이 좋습니다.
현재 구현은 일반 clickable() 구조라서 접근성 서비스가 "현재 선택된 탭"과 탭 그룹 관계를 인식하지 못합니다. Row에 selectableGroup() 모디파이어를 추가하고, 각 아이템의 Column에 selectable(selected = isSelected, role = Role.Tab) 모디파이어를 적용하면 스크린 리더 사용자에게 상호 배타적 선택 UI임이 명확해집니다. 현재 Compose BOM 버전(2024.12.01)에서 모든 필수 API가 안정적으로 제공되므로 구현 가능합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/BottomNavBar.kt`
around lines 36 - 49, BottomNavBar's Row should be marked as a selectable group
and each tab item must be selectable so accessibility services recognize the
exclusive selection: add modifier.selectableGroup() to the Row in BottomNavBar
(where items.forEach is iterating and isSelected computed from currentRoute) and
update the item composable (BottomNavBarItem or the Column inside it) to include
selectable(selected = isSelected, role = Role.Tab) instead of a plain clickable;
keep onItemClick(item.key) as the selection action so semantics and focus/role
are properly exposed to screen readers.
| is RootNavRoute.WelcomeNavRoute -> NavEntry(route) { | ||
| WelcomeScreen( | ||
| onNextClick = { | ||
| rootBackStack.clear() | ||
| rootBackStack.add(RootNavRoute.RegisterNavRoute) | ||
| } | ||
| ) | ||
| } | ||
|
|
||
| is RootNavRoute.RegisterNavRoute -> NavEntry(route) {} |
There was a problem hiding this comment.
회원가입 플로우가 여기서 빈 화면으로 끊깁니다.
WelcomeScreen의 onNextClick은 RegisterNavRoute로 이동시키는데, 해당 destination이 빈 NavEntry라서 사용자가 다음 단계에서 더 진행할 수 없습니다. 실제 회원가입 화면을 연결하거나, 준비 전까지는 임시 placeholder라도 넣어 흐름이 멈추지 않게 해주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootNavigation.kt`
around lines 64 - 73, The RegisterNavRoute destination is currently an empty
NavEntry which breaks the signup flow; replace the empty NavEntry for
RootNavRoute.RegisterNavRoute with the actual registration UI component (e.g.,
RegisterScreen) or at minimum a temporary placeholder screen so WelcomeScreen's
onNextClick (which pushes RootNavRoute.RegisterNavRoute) leads to a usable
destination. Update the NavEntry for RootNavRoute.RegisterNavRoute to invoke
RegisterScreen(...) or a simple PlaceholderRegisterScreen that exposes the next
actions, and ensure any required callbacks (e.g., onComplete/onBack) match the
existing navigation stack usage.
| private val _startDestination = MutableStateFlow<RootNavRoute?>(null) | ||
| val startDestination: StateFlow<RootNavRoute?> = _startDestination.asStateFlow() |
There was a problem hiding this comment.
토큰 조회 예외가 나면 루트 네비게이션이 초기 상태에 멈춥니다.
accessToken.first()가 예외를 던지면 _startDestination가 계속 null이라 시작 화면 결정이 끝나지 않습니다. 실패 시 LoginNavRoute로 폴백하거나, 로딩/에러를 분리한 상태로 전환해 주세요.
예시 수정안
+import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.gildongmu.dduru.data.local.TokenLocalDataSource
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@@
private fun checkLoginStatus() {
viewModelScope.launch {
- val accessToken = tokenLocalDataSource.accessToken.first()
- if (accessToken.isNullOrBlank()) {
- _startDestination.value = RootNavRoute.LoginNavRoute
- } else {
- _startDestination.value = RootNavRoute.MainNavRoute
- }
+ _startDestination.value = try {
+ val accessToken = tokenLocalDataSource.accessToken.first()
+ if (accessToken.isNullOrBlank()) {
+ RootNavRoute.LoginNavRoute
+ } else {
+ RootNavRoute.MainNavRoute
+ }
+ } catch (t: Throwable) {
+ Log.e("RootViewModel", "Failed to resolve start destination", t)
+ RootNavRoute.LoginNavRoute
+ }
}
}
}As per coding guidelines "에러 처리 전략이 일관적인지 확인 (UiState.Error, Result 등으로 UI가 예측 가능하게 상태를 표현하는지)".
Also applies to: 26-34
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@app/src/main/java/com/gildongmu/dduru/presentation/navigation/RootViewModel.kt`
around lines 19 - 20, accessToken.first() 예외 발생 시 _startDestination이 null로 남아 루트
네비게이션이 멈추는 문제가 있습니다; accessToken.first() 호출(또는 이를 사용하는 함수/블록)을 try/catch로 감싸고 예외
발생 시 _startDestination에 LoginNavRoute를 할당하거나(startDestination 흐름을 로그인 경로로 폴백) 대신
로딩/에러 상태를 표현할 수 있는 별도 UiState(예: RootUiState.Loading/RootUiState.Error)를 내보내도록
수정하세요; 관련 심볼: _startDestination, startDestination, accessToken.first(),
LoginNavRoute, RootNavRoute.
| #navigation | ||
| navigation3 = "1.0.1" | ||
| lifecycleViewmodelNav3 = "2.10.0" | ||
| haze = "0.7.3" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
androidx navigation3 library latest version 2026
💡 Result:
As of March 8, 2026, the AndroidX Navigation3 latest versions are:
- Latest stable:
1.0.1(released Feb 25, 2026) - Latest alpha:
1.1.0-alpha05(released Feb 25, 2026)
Example Gradle deps:
androidx.navigation3:navigation3-runtime:1.1.0-alpha05androidx.navigation3:navigation3-ui:1.1.0-alpha05[1]
Sources: [1]
🌐 Web query:
androidx lifecycle viewmodel navigation3 latest version 2026
💡 Result:
As of March 8, 2026:
-
AndroidX Lifecycle ViewModel (
androidx.lifecycle:lifecycle-viewmodel)- Latest stable Lifecycle line: 2.9.3 (so
lifecycle-viewmodel:2.9.3) [1] - Latest pre-release: 2.10.0-rc01 (so
lifecycle-viewmodel:2.10.0-rc01) [1]
- Latest stable Lifecycle line: 2.9.3 (so
-
AndroidX Navigation3 (
androidx.navigation3:navigation3-*)- Latest stable: 1.0.1 (e.g.,
androidx.navigation3:navigation3-runtime-android:1.0.1) [2] - Latest pre-release: 1.1.0-alpha03 [2]
- Latest stable: 1.0.1 (e.g.,
Sources: AndroidX release notes for Lifecycle and Navigation3. [1][2]
lifecycleViewmodelNav3 버전을 현재 출시된 버전으로 업데이트하세요.
현재 2.10.0으로 설정되어 있으나, 이 버전은 아직 릴리스되지 않았습니다. androidx.lifecycle:lifecycle-viewmodel-navigation3의 최신 안정 버전은 2.9.3입니다. (2.10.0은 2.10.0-rc01 프리릴리스로만 사용 가능)
navigation3 = "1.0.1"과 haze = "0.7.3"은 정확합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@gradle/libs.versions.toml` around lines 69 - 72, Update the
lifecycleViewmodelNav3 version constant from "2.10.0" to the latest stable
release "2.9.3": locate the lifecycleViewmodelNav3 entry in the
libs.versions.toml and change its value to "2.9.3" (leave navigation3 and haze
unchanged).
✨ 작업 내용
전체 네비게이션, 메인 네비게이션 및 UI구현했습니다
📄 상세 내용
주요 역할
• 하단 네비게이션 UI 구성
• 현재 선택된 Route에 따라 UI 상태 변경
• Navigation 클릭 이벤트 전달
특징
• sealed interface를 사용
• 문자열 route 대신 타입 기반 navigation 사용
👀 To Reviewer
커다란 흐름(로그인 -> WelcomeScreen -> 회원가입 -> 홈화면)만 root로 뺴고 나머지는 자체 상세 네비게이션을 구현할 예정입니다.
Navigation 구조 설계나 네이밍에 대해 개선 의견이 있다면 피드백 부탁드립니다.
📷 스크린샷 / 영상
Screen_recording_20260308_205043.mp4
Summary by CodeRabbit
릴리스 노트
새로운 기능
리팩토링
종속성