Conversation
| import kotlinx.coroutines.flow.stateIn | ||
|
|
||
| @Stable | ||
| class MainAppState( |
There was a problem hiding this comment.
MainAppState라는 이름이 조금 애매한 것 같다는 생각도 드는데
이렇게 클래스명을 지으신 이유가 궁금합니다!
There was a problem hiding this comment.
좋은 질문이네용 !! MainAppState라는 네이밍을 선택한 이유를 설명드리겠습니다.
이 클래스는 단순히 네비게이션만 담당하는 게 아니라 앱의 UI 상태를 종합적으로 관리하기 때문에 MainAppState라는 이름을 사용했습니다. 코드를 보시면 navController 외에도 currentTab, isBottomBarVisible 같은 UI 상태들을 함께 관리하고 있죠. 만약 MainNavigator라고 했다면 네비게이션 기능만 강조되어서 이런 UI 상태 관리 역할이 명확하게 드러나지 않을 것 같았습니다!
MainAppState라는 이름은 이 클래스가 Main 화면의 최상위 레벨에서 앱의 전반적인 상태를 관리한다는 의미를 가장 명확하게 전달한다고 생각합니다. 나중에 snackbarHostState나 isNetworkAvailable 같은 다른 상태들을 추가할 때도 자연스럽게 확장할 수 있구요!
| <?xml version="1.0" encoding="utf-8"?> | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| xmlns:tools="http://schemas.android.com/tools" > | ||
|
|
There was a problem hiding this comment.
인터넷 퍼미션 넣어놓는거 어떠세요??
나중에 필요하면 넣을까요??
There was a problem hiding this comment.
왁 코드레빗이랑 함께 추가해둘게용 ~~
Walkthrough앱 초기화: Gradle 설정·래퍼, CI·CODEOWNERS, Android 매니페스트, Hilt·Timber 초기화, 네트워크·DataStore·레포지토리/데이터 소스, Compose 디자인 시스템·네비게이션·탭 UI, 공통 확장유틸, 리소스 및 테스트 파일을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as 사용자
participant MainActivity as MainActivity
participant CherrishTheme as CherrishTheme
participant MainAppState as MainAppState
participant NavController as NavController
participant NavHost as NavHost
Note over MainActivity,MainAppState: 앱 시작 — Compose 설정 및 상태 초기화
User->>MainActivity: 앱 실행 / 탭 선택
MainActivity->>CherrishTheme: setContent { CherrishTheme { ... } }
MainActivity->>MainAppState: onTabSelected(tab)
MainAppState->>NavController: navigateTo{TargetTab}()
NavController->>NavHost: 변경 요청 (popUpTo/restoreState/launchSingleTop)
NavHost-->>MainActivity: 해당 Route의 Composable 렌더링
Note right of MainAppState: currentTab / isBottomBarVisible는 StateFlow로 관찰되어 UI에 반영
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Pre-merge checks❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 13
🧹 Nitpick comments (29)
app/src/main/java/com/cherrish/android/core/common/state/UiState.kt (1)
3-13: Failure 상태에 에러 정보 추가를 고려하세요.현재 구현은 일반적인 UI 상태 패턏을 잘 따르고 있습니다. sealed interface와 공변성 사용이 적절합니다.
다만,
Failure상태가 에러 정보(에러 메시지, 예외 객체, 에러 코드 등)를 포함하지 않아 실제 사용 시 에러 처리와 사용자 피드백 제공에 제한이 있을 수 있습니다.🔎 에러 정보를 포함하는 개선안
sealed interface UiState<out T> { data object Empty : UiState<Nothing> data object Loading : UiState<Nothing> data class Success<T>( val data: T ) : UiState<T> - data object Failure : UiState<Nothing> + data class Failure( + val error: Throwable? = null, + val message: String? = null + ) : UiState<Nothing> }app/src/main/java/com/cherrish/android/core/local/datastore/PreferencesDataStore.kt (1)
7-14: 빈 래퍼 클래스의 필요성을 검토해주세요.현재
PreferencesDataStore클래스는 주석으로 예시만 포함하고 있으며 실제 구현된 메서드가 없습니다. 초기 세팅 단계에서 구조만 잡아둔 것으로 보이는데, 두 가지 옵션을 고려해볼 수 있습니다:
- 당장 사용 예정이라면 주석의 예시 메서드를 실제로 구현
- 나중에 구현 예정이라면 클래스 상단에 해당 의도를 명시하는 KDoc 추가
현재 상태로는
DataStore<Preferences>를 직접 주입받아 사용하는 것과 차이가 없어 보입니다.💡 KDoc 추가 예시
+/** + * DataStore Preferences를 관리하는 래퍼 클래스입니다. + * 향후 토큰 저장/조회 등의 기능이 추가될 예정입니다. + */ class PreferencesDataStore @Inject constructor( private val dataStore: DataStore<Preferences> ) {app/src/main/java/com/cherrish/android/core/local/di/DataStoreModule.kt (1)
29-33: 불필요한 provide 메서드를 제거하는 것을 권장합니다.
PreferencesDataStore는 이미@Inject생성자를 가지고 있어 Hilt가 자동으로 의존성을 제공할 수 있습니다. 따라서 이@Provides메서드는 중복됩니다.Hilt는 다음 조건을 만족하는 클래스를 자동으로 제공합니다:
@Inject생성자 보유- 생성자의 모든 파라미터가 의존성 그래프에 존재
현재
PreferencesDataStore는 이 조건을 만족하므로 별도의 provide 메서드 없이도 주입 가능합니다.🔎 제안하는 리팩토링
@Provides @Singleton fun provideDataStore( @ApplicationContext context: Context ): DataStore<Preferences> = context.dataStore - - @Provides - @Singleton - fun providePreferencesDataStore( - dataStore: DataStore<Preferences> - ): PreferencesDataStore = PreferencesDataStore(dataStore) }이렇게 수정해도
PreferencesDataStore는 여전히 다른 클래스에서@Inject로 주입받을 수 있습니다.app/src/main/java/com/cherrish/android/core/common/extension/FlowExt.kt (1)
11-33: KDoc 문서화를 추가하는 것을 권장합니다.재사용 가능한 유틸리티 함수에 대한 문서화가 없어 다른 개발자가 함수의 목적과 사용법을 이해하기 어렵습니다. 특히
key파라미터의 용도와 사용 시기에 대한 설명이 필요합니다.🔎 제안하는 문서화 예시
+/** + * Lifecycle-aware하게 Flow를 collect하는 Composable 확장 함수입니다. + * Flow는 lifecycle이 STARTED 상태일 때만 수집되며, 자동으로 취소됩니다. + * + * @param key LaunchedEffect를 재시작할 추가 키. Flow나 lifecycle 외에 + * 추가로 effect를 재시작하고 싶을 때 사용합니다. + * @param collector Flow에서 방출된 값을 처리하는 suspend 함수 + */ @Composable fun <T> Flow<T>.collectSideEffect( key: Any? = Unit, collector: suspend (T) -> Unit ) { +/** + * Lifecycle-aware하게 Flow를 collectLatest로 수집하는 Composable 확장 함수입니다. + * 새로운 값이 방출되면 이전 collector 실행을 취소하고 최신 값만 처리합니다. + * Flow는 lifecycle이 STARTED 상태일 때만 수집됩니다. + * + * @param key LaunchedEffect를 재시작할 추가 키. Flow나 lifecycle 외에 + * 추가로 effect를 재시작하고 싶을 때 사용합니다. + * @param collector Flow에서 방출된 최신 값을 처리하는 suspend 함수 + */ @Composable fun <T> Flow<T>.collectLatestSideEffect( key: Any? = Unit, collector: suspend (T) -> Unit ) {app/src/main/java/com/cherrish/android/core/network/JsonStringExt.kt (1)
3-5: JSON 타입 확인 로직 개선 권장현재 구현은 JSON 유효성을 완전히 검증하지 않습니다.
isJsonObject()와isJsonArray()는 시작/끝 문자만 확인하므로"{invalid}","[incomplete"같은 잘못된 문자열도true를 반환합니다. 이들 함수는NetworkModule.kt의 HttpLoggingInterceptor에서JSONObject()/JSONArray()생성자 호출 전 가벼운 타입 체크로 사용 중이므로, 생성자가 실제 검증을 수행하지만, 다음과 같은 개선이 가능합니다:
- 가독성 개선:
this?.startsWith() == true && this.endsWith()패턴은 short-circuit으로 안전하지만, 명시적 null 체크가 더 명확합니다.- 공백 처리:
" { } "같은 앞뒤 공백이 있는 경우 정상적으로 처리하도록 trim() 추가 검토- KDoc 추가: 이것이 경량 체크(완전한 JSON 검증 아님)임을 문서화
가독성 개선 제안
-fun String?.isJsonObject(): Boolean = this?.startsWith("{") == true && this.endsWith("}") +fun String?.isJsonObject(): Boolean = + this != null && this.startsWith("{") && this.endsWith("}") -fun String?.isJsonArray(): Boolean = this?.startsWith("[") == true && this.endsWith("]") +fun String?.isJsonArray(): Boolean = + this != null && this.startsWith("[") && this.endsWith("]")또는 공백 처리 포함:
-fun String?.isJsonObject(): Boolean = this?.startsWith("{") == true && this.endsWith("}") +fun String?.isJsonObject(): Boolean { + val trimmed = this?.trim() ?: return false + return trimmed.startsWith("{") && trimmed.endsWith("}") +} -fun String?.isJsonArray(): Boolean = this?.startsWith("[") == true && this.endsWith("]") +fun String?.isJsonArray(): Boolean { + val trimmed = this?.trim() ?: return false + return trimmed.startsWith("[") && trimmed.endsWith("]") +}app/src/main/res/values/strings.xml (1)
1-3: 파일 끝 줄바꿈(newline) 추가를 고려하세요.현재 파일이 마지막 닫는 태그 뒤에 줄바꿈 없이 끝나고 있습니다. 일반적인 개발 관행 및 POSIX 표준에 따라 모든 텍스트 파일은 최종 줄바꿈으로 끝나는 것이 권장됩니다.
🔎 제안된 수정
<resources> <string name="app_name">Cherrish</string> -</resources> \ No newline at end of file +</resources>app/src/test/java/com/cherrish/android/ExampleUnitTest.kt (2)
5-5: 와일드카드 임포트를 명시적 임포트로 변경하는 것을 고려하세요.와일드카드 임포트(
import org.junit.Assert.*)는 코드 가독성을 저하시키고 네임스페이스 충돌 가능성을 높입니다. 명시적으로 필요한 함수만 임포트하는 것을 권장합니다.🔎 명시적 임포트로 변경
-import org.junit.Assert.* +import org.junit.Assert.assertEquals
12-17: 실제 비즈니스 로직 테스트로 교체하는 것을 고려하세요.이 예제 테스트는 초기 세팅 시 기본으로 생성된 보일러플레이트입니다. 프로젝트가 진행됨에 따라 실제 기능을 테스트하는 의미 있는 테스트 케이스로 교체하거나 제거하는 것을 권장합니다.
app/src/main/java/com/cherrish/android/data/remote/dto/response/DummyResponseDto.kt (2)
6-14: @SerialName 어노테이션의 필요성을 검토하세요.필드명과 JSON 키가 동일한 경우(
id,name,age)@SerialName어노테이션은 불필요합니다. kotlinx.serialization은 기본적으로 필드명을 JSON 키로 사용합니다. 단, 향후 JSON API 스펙이 변경될 가능성이 있거나 명시적 매핑을 선호하는 팀 컨벤션이 있다면 유지할 수 있습니다.🔎 중복 어노테이션 제거 예시
@Serializable data class DummyResponseDto( - @SerialName("id") val id: Long, - @SerialName("name") val name: String, - @SerialName("age") val age: Int )
7-14: 더미 코드 제거 계획을 확인하세요.
DummyResponseDto는 초기 세팅을 위한 예제 코드입니다. 실제 API 응답 DTO가 구현되면 이 더미 클래스와 관련된 코드(DummyDataSource, DummyService, DummyRepository 등)를 제거하거나 실제 구현으로 교체해야 합니다.다음 스크립트로 더미 관련 코드의 범위를 확인할 수 있습니다:
#!/bin/bash # Description: Find all dummy-related code files echo "=== Dummy related files ===" fd -e kt -x grep -l "Dummy" {} echo -e "\n=== Dummy class/interface declarations ===" ast-grep --pattern 'class Dummy$_' ast-grep --pattern 'interface Dummy$_'.gitignore (1)
24-24:.idea/패턴 중복 고려사항Line 24에서
.idea/전체를 제외하고 있어, Lines 178-202의 개별.idea/파일 패턴들이 중복됩니다. 현재 상태로도 문제없이 동작하지만, 코드 정리 차원에서 개별 패턴들을 제거할 수 있습니다.Also applies to: 178-202
.github/workflows/pr_checker.yml (2)
8-56: lint와 build 작업 간 중복 설정을 고려해 보세요.두 작업에서 checkout, cache, JDK 설정, Android SDK 설정, local.properties 생성, gradlew 권한 부여 단계가 동일하게 반복됩니다. 향후 유지보수성을 위해 composite action이나 reusable workflow로 공통 단계를 추출하는 것을 고려해 보세요.
또한 현재 CI에서 단위 테스트(
./gradlew test)가 실행되지 않습니다. 테스트 커버리지 확보를 위해 추가를 권장합니다.
50-55: 멀티 모듈 확장 시 ktlint 리포트 경로 확인 필요.현재
app/build/reports/ktlint/경로만 업로드됩니다. 향후 멀티 모듈 구조로 확장할 경우, 와일드카드 패턴(**/build/reports/ktlint/)을 사용하여 모든 모듈의 리포트를 수집하는 것을 고려해 보세요.app/src/main/java/com/cherrish/android/presentation/home/HomeScreen.kt (1)
9-25: LGTM! Route/Screen 분리 패턴이 잘 적용되어 있습니다.공개
HomeRoute와 비공개HomeScreen의 분리가 적절합니다. 향후 상태 관리나 ViewModel 연동 시 이 구조가 확장에 유리합니다.선택적 개선 사항:
HomeRoute에도modifier파라미터를 노출하면 호출부에서 추가적인 modifier 적용이 가능해집니다.🔎 Modifier 노출 예시
@Composable fun HomeRoute( - paddingValues: PaddingValues + paddingValues: PaddingValues, + modifier: Modifier = Modifier ) { - HomeScreen(paddingValues = paddingValues) + HomeScreen(paddingValues = paddingValues, modifier = modifier) }app/src/main/java/com/cherrish/android/core/designsystem/theme/Color.kt (1)
5-11: 템플릿 색상을 브랜드 컬러로 교체하는 것을 권장합니다.현재 정의된 색상들은 Android Studio의 기본 템플릿 색상입니다. 초기 세팅 단계에서는 적절하지만, 향후 Cherrish 앱의 브랜드 아이덴티티를 반영한 커스텀 색상으로 교체하는 것이 좋습니다. 또한
Purple80,Pink40같은 일반적인 이름 대신Primary,Secondary,OnSurface등 의미론적 네이밍을 고려해보세요.디자인 시스템이 확정되면 색상 팔레트 구조 제안을 도와드릴 수 있습니다.
app/src/main/java/com/cherrish/android/presentation/calendar/CalendarScreen.kt (2)
9-14: Preview 어노테이션 추가를 고려해보세요.개발 편의성을 위해
CalendarRoute에@Preview어노테이션을 추가하면 Android Studio의 Compose Preview에서 UI를 빠르게 확인할 수 있습니다.🔎 Preview 추가 예시
+@Preview(showBackground = true) +@Composable +private fun CalendarRoutePreview() { + CalendarRoute(paddingValues = PaddingValues()) +} + @Composable fun CalendarRoute( paddingValues: PaddingValues
16-25: 향후 상태 관리 아키텍처 구현을 권장합니다.현재는 플레이스홀더 구현으로 적절하지만, 실제 기능 구현 시 ViewModel, UiState, 단방향 데이터 플로우 패턴을 적용하는 것이 좋습니다.
코딩 가이드라인에 따라 Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중해주세요.
ViewModel과 UiState 구조 예시가 필요하시면 도움 드릴 수 있습니다.
app/src/main/java/com/cherrish/android/presentation/main/MainTab.kt (2)
17-17: 플레이스홀더 아이콘을 실제 아이콘으로 교체해주세요.모든 탭이
ic_launcher_background를 아이콘으로 사용하고 있습니다. 초기 세팅 단계에서는 적절하지만, 각 탭의 역할을 나타내는 고유한 아이콘으로 교체가 필요합니다.Also applies to: 22-22, 27-27, 32-32
37-45: Companion 헬퍼 메서드 필요성을 재검토해보세요.
find()와contains()메서드는 Kotlin의 표준entries.find(predicate)및entries.any(predicate)와 동일한 기능을 제공합니다. 추가적인 로직이 없다면 표준 라이브러리 메서드를 직접 사용하는 것이 더 명확할 수 있습니다.🔎 사용 예시 비교
현재 방식:
MainTab.find { it.route == someRoute }표준 라이브러리 직접 사용:
MainTab.entries.find { it.route == someRoute }추가 로직이 필요하거나 명확한 의도 전달이 중요하다면 현재 방식도 충분히 유효합니다.
app/src/main/java/com/cherrish/android/core/common/navigation/MainTabRoute.kt (1)
1-3: LGTM! 네비게이션 마커 인터페이스가 올바르게 정의되었습니다.Route를 확장하는 MainTabRoute 인터페이스가 탭 네비게이션의 타입 계층을 명확하게 정의하고 있습니다.
만약 모든 MainTabRoute 구현체가 예측 가능한 data object들이라면,
sealed interface로 변경하면 컴파일 타임 타입 안정성을 강화하고 when 표현식에서 exhaustive 체크를 받을 수 있습니다.🔎 선택적 개선: sealed interface 사용
-interface MainTabRoute : Route +sealed interface MainTabRoute : Routeapp/src/main/java/com/cherrish/android/presentation/mypage/MyPageScreen.kt (1)
9-14:modifier파라미터가 Route에서 전달되지 않습니다.
MyPageScreen에modifier파라미터가 정의되어 있지만,MyPageRoute에서 호출할 때 전달되지 않아 기본값만 사용됩니다. 향후 확장성을 위해 Route 레벨에서도 modifier를 받아 전달하는 것을 고려해주세요.🔎 수정 제안
@Composable fun MyPageRoute( - paddingValues: PaddingValues + paddingValues: PaddingValues, + modifier: Modifier = Modifier ) { - MyPageScreen(paddingValues = paddingValues) + MyPageScreen(paddingValues = paddingValues, modifier = modifier) }app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt (1)
26-31: 불필요한 재구성 시 리스트 재생성을 최적화할 수 있습니다.Line 28의
MainTab.entries.toPersistentList()가 매 recomposition마다 새 리스트를 생성합니다.MainTab.entries는 불변이므로 remember를 사용하거나 컴포저블 외부에서 정의하여 최적화할 수 있습니다.🔎 최적화 제안
방법 1: remember 사용
+ val tabs = remember { MainTab.entries.toPersistentList() } + Scaffold( bottomBar = { MainBottomBar( visible = isBottomBarVisible, - tabs = MainTab.entries.toPersistentList(), + tabs = tabs, currentTab = currentTab, onTabSelected = appState::navigate )방법 2: 파일 최상위 레벨에 정의
private val mainTabs = MainTab.entries.toPersistentList() @Composable fun MainScreen(appState: MainAppState) { // ... MainBottomBar( tabs = mainTabs, // ... ) }app/src/main/java/com/cherrish/android/data/repositoryimpl/DummyRepositoryImpl.kt (1)
13-16:data!!사용 시 null 데이터에 대한 의미 있는 에러 처리 필요
data!!가 null인 경우NullPointerException이 발생하고, 이는suspendRunCatching에 의해Result.failure로 반환됩니다. 하지만 이렇게 하면 실제 문제(서버 응답에 데이터가 없음)가 일반적인 NPE로 가려집니다.🔎 의미 있는 에러 메시지를 포함한 수정 제안
override suspend fun getDummy(): Result<DummyModel> = suspendRunCatching { - dummyDataSource.getDummyData().data!!.toModel() + requireNotNull(dummyDataSource.getDummyData().data) { + "Dummy data response is null" + }.toModel() }app/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.kt (2)
35-41: 동적 색상 기본값 설정 재검토 필요
dynamicColor의 기본값이true로 설정되어 있어, Android 12+ 기기에서는 항상 시스템 색상을 따릅니다. 이는 앱의 브랜드 아이덴티티를 유지하는 데 방해가 될 수 있습니다. 앱이 고유한 브랜드 색상을 가지고 있다면false를 기본값으로 고려하세요.🔎 수정 제안
@Composable fun CherrishTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, + dynamicColor: Boolean = false, content: @Composable () -> Unit ) {
24-32: 주석처리된 기본 색상 코드 정리 필요Material3 기본 색상들이 주석으로 남아있습니다. 이후 커스터마이징 시 참고용으로 유용할 수 있지만, 프로덕션 코드에서는 제거하거나 실제로 사용하는 것을 권장합니다.
app/src/main/java/com/cherrish/android/core/network/NetworkModule.kt (1)
62-68: OkHttpClient에 타임아웃 설정 추가 권장네트워크 요청에 대한 타임아웃이 설정되지 않아 무한 대기 상황이 발생할 수 있습니다.
connectTimeout,readTimeout,writeTimeout을 설정하여 네트워크 안정성을 개선하세요.🔎 타임아웃 설정 추가 제안
+import java.util.concurrent.TimeUnit + @Provides @Singleton fun provideDefaultOkHttpClient( loggingInterceptor: Interceptor ): OkHttpClient = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) .build()app/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.kt (2)
84-93: 조건부 색상 선택 최적화 가능
if (selected)표현식이 Icon과 Text에서 각각 평가되어 불필요한 계산이 반복됩니다. 색상을 미리 계산하여 재사용하면 가독성과 성능을 개선할 수 있습니다.🔎 색상 미리 계산 제안
@Composable private fun RowScope.MainBottomBarItem( tab: MainTab, selected: Boolean, onClick: () -> Unit ) { + val tintColor = if (selected) Color.Black else Color.Gray + Column( modifier = Modifier.weight(1f) .clickable(onClick = onClick), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) { Icon( imageVector = ImageVector.vectorResource(id = tab.iconRes), modifier = Modifier.size(24.dp), contentDescription = null, - tint = if (selected) Color.Black else Color.Gray + tint = tintColor ) Text( text = tab.label, - color = if (selected) Color.Black else Color.Gray + color = tintColor ) } }
79-80: clickable modifier 위치 조정 권장
Modifier.weight(1f).clickable(onClick = onClick)순서에서clickable을 먼저 적용하면 리플 효과가 weight 영역 전체에 적용됩니다. 현재 순서도 작동하지만, 일반적으로 clickable을 먼저 적용하는 것이 권장됩니다.🔎 modifier 순서 수정 제안
Column( - modifier = Modifier.weight(1f) - .clickable(onClick = onClick), + modifier = Modifier + .weight(1f) + .clickable(onClick = onClick), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(4.dp) ) {app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
22-59: StateFlow 기반 상태 관리 구조가 잘 설계되었습니다.
@Stable어노테이션 사용과stateIn을 통한 StateFlow 변환이 적절하며,WhileSubscribed(5_000)정책으로 메모리 효율성도 고려되었습니다. 반응형 상태 관리 패턴이 잘 적용되었습니다.다만 선택적으로 개선할 수 있는 부분들이 있습니다:
null 초기값 처리:
currentDestination과currentTab의initialValue가null입니다. 소비하는 측에서 null 처리가 필요한데,startDestination을 활용하여 초기값을 제공하는 것도 고려해볼 수 있습니다.공개 API 문서화: public 프로퍼티들에 대한 KDoc 추가를 권장합니다. 특히
currentTab과isBottomBarVisible의 null 가능성과 의미를 명확히 하면 좋습니다.🔎 선택적 개선사항: null 처리 예시
val currentTab: StateFlow<MainTab?> = currentDestination .map { destination -> MainTab.find { tab -> destination?.hasRoute(tab.route::class) == true } } .stateIn( scope = coroutineScope, started = SharingStarted.WhileSubscribed(5_000), - initialValue = null + initialValue = MainTab.HOME // 또는 MainTab.fromRoute(startDestination) )
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (11)
app/src/main/res/mipmap-hdpi/ic_launcher.webpis excluded by!**/*.webpapp/src/main/res/mipmap-hdpi/ic_launcher_round.webpis excluded by!**/*.webpapp/src/main/res/mipmap-mdpi/ic_launcher.webpis excluded by!**/*.webpapp/src/main/res/mipmap-mdpi/ic_launcher_round.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xhdpi/ic_launcher.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xxhdpi/ic_launcher.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webpis excluded by!**/*.webpapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webpis excluded by!**/*.webpgradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jar
📒 Files selected for processing (73)
.coderabbit.yaml.github/CODEOWNERS.github/ISSUE_TEMPLATE/issue_template.md.github/workflows/pr_checker.yml.gitignore.run/Cherrish [ktLintCheck].run.xml.run/Cherrish [ktLintFormat].run.xmlapp/.gitignoreapp/build.gradle.ktsapp/proguard-rules.proapp/src/androidTest/java/com/cherrish/android/ExampleInstrumentedTest.ktapp/src/main/AndroidManifest.xmlapp/src/main/java/com/cherrish/android/CherrishApplication.ktapp/src/main/java/com/cherrish/android/core/common/extension/FlowExt.ktapp/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.ktapp/src/main/java/com/cherrish/android/core/common/extension/RunCatchingExt.ktapp/src/main/java/com/cherrish/android/core/common/extension/StateFlowExt.ktapp/src/main/java/com/cherrish/android/core/common/navigation/MainTabRoute.ktapp/src/main/java/com/cherrish/android/core/common/navigation/Route.ktapp/src/main/java/com/cherrish/android/core/common/state/UiState.ktapp/src/main/java/com/cherrish/android/core/designsystem/component/.gitkeepapp/src/main/java/com/cherrish/android/core/designsystem/theme/Color.ktapp/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.ktapp/src/main/java/com/cherrish/android/core/designsystem/theme/Type.ktapp/src/main/java/com/cherrish/android/core/local/datastore/PreferencesDataStore.ktapp/src/main/java/com/cherrish/android/core/local/di/DataStoreModule.ktapp/src/main/java/com/cherrish/android/core/network/BaseResponse.ktapp/src/main/java/com/cherrish/android/core/network/JsonStringExt.ktapp/src/main/java/com/cherrish/android/core/network/NetworkModule.ktapp/src/main/java/com/cherrish/android/core/util/SuspendRunCatching.ktapp/src/main/java/com/cherrish/android/data/di/DataSourceModule.ktapp/src/main/java/com/cherrish/android/data/di/RepositoryModule.ktapp/src/main/java/com/cherrish/android/data/di/ServiceModule.ktapp/src/main/java/com/cherrish/android/data/local/datasource/.gitkeepapp/src/main/java/com/cherrish/android/data/local/datasourceimpl/.gitkeepapp/src/main/java/com/cherrish/android/data/model/DummyModel.ktapp/src/main/java/com/cherrish/android/data/remote/datasource/DummyDataSource.ktapp/src/main/java/com/cherrish/android/data/remote/datasourceimpl/DummyDataSourceImpl.ktapp/src/main/java/com/cherrish/android/data/remote/dto/request/.gitkeepapp/src/main/java/com/cherrish/android/data/remote/dto/response/DummyResponseDto.ktapp/src/main/java/com/cherrish/android/data/remote/service/DummyService.ktapp/src/main/java/com/cherrish/android/data/repository/DummyRepository.ktapp/src/main/java/com/cherrish/android/data/repositoryimpl/DummyRepositoryImpl.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarScreen.ktapp/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.ktapp/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.ktapp/src/main/java/com/cherrish/android/presentation/challenge/navigation/ChallengeNavigator.ktapp/src/main/java/com/cherrish/android/presentation/home/HomeScreen.ktapp/src/main/java/com/cherrish/android/presentation/home/navigation/HomeNavigator.ktapp/src/main/java/com/cherrish/android/presentation/main/MainActivity.ktapp/src/main/java/com/cherrish/android/presentation/main/MainAppState.ktapp/src/main/java/com/cherrish/android/presentation/main/MainScreen.ktapp/src/main/java/com/cherrish/android/presentation/main/MainTab.ktapp/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.ktapp/src/main/java/com/cherrish/android/presentation/mypage/MyPageScreen.ktapp/src/main/java/com/cherrish/android/presentation/mypage/navigation/MyPageNavigator.ktapp/src/main/res/drawable/ic_launcher_background.xmlapp/src/main/res/drawable/ic_launcher_foreground.xmlapp/src/main/res/mipmap-anydpi/ic_launcher.xmlapp/src/main/res/mipmap-anydpi/ic_launcher_round.xmlapp/src/main/res/values/colors.xmlapp/src/main/res/values/strings.xmlapp/src/main/res/values/themes.xmlapp/src/main/res/xml/backup_rules.xmlapp/src/main/res/xml/data_extraction_rules.xmlapp/src/test/java/com/cherrish/android/ExampleUnitTest.ktbuild.gradle.ktsgradle.propertiesgradle/libs.versions.tomlgradle/wrapper/gradle-wrapper.propertiesgradlewgradlew.batsettings.gradle.kts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: - Jetpack Compose 구조, 상태 관리, recomposition 최적화에 집중
- ViewModel, UiState, 단방향 데이터 흐름 패턴 검토
- 불필요한 recomposition 가능성, remember/derivedStateOf 적절한 사용 확인
- 네이밍 컨벤션, 가독성, Google 권장 Android 아키텍처 준수 여부
Files:
app/src/main/java/com/cherrish/android/core/common/navigation/Route.ktapp/src/main/java/com/cherrish/android/data/remote/service/DummyService.ktapp/src/main/java/com/cherrish/android/core/common/navigation/MainTabRoute.ktapp/src/main/java/com/cherrish/android/presentation/challenge/navigation/ChallengeNavigator.ktapp/src/main/java/com/cherrish/android/core/designsystem/theme/Type.ktapp/src/main/java/com/cherrish/android/core/common/state/UiState.ktapp/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.ktapp/src/main/java/com/cherrish/android/core/util/SuspendRunCatching.ktapp/src/main/java/com/cherrish/android/data/di/RepositoryModule.ktapp/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.ktapp/src/main/java/com/cherrish/android/data/di/DataSourceModule.ktapp/src/main/java/com/cherrish/android/presentation/main/MainActivity.ktapp/src/main/java/com/cherrish/android/presentation/home/HomeScreen.ktapp/src/main/java/com/cherrish/android/core/common/extension/RunCatchingExt.ktapp/src/main/java/com/cherrish/android/core/designsystem/theme/Color.ktapp/src/main/java/com/cherrish/android/presentation/mypage/MyPageScreen.ktapp/src/main/java/com/cherrish/android/data/model/DummyModel.ktapp/src/main/java/com/cherrish/android/data/repository/DummyRepository.ktapp/src/test/java/com/cherrish/android/ExampleUnitTest.ktapp/src/main/java/com/cherrish/android/presentation/main/MainScreen.ktapp/src/main/java/com/cherrish/android/core/local/datastore/PreferencesDataStore.ktapp/src/main/java/com/cherrish/android/presentation/calendar/CalendarScreen.ktapp/src/main/java/com/cherrish/android/core/network/JsonStringExt.ktapp/src/main/java/com/cherrish/android/data/di/ServiceModule.ktapp/src/main/java/com/cherrish/android/presentation/home/navigation/HomeNavigator.ktapp/src/main/java/com/cherrish/android/core/common/extension/StateFlowExt.ktapp/src/main/java/com/cherrish/android/CherrishApplication.ktapp/src/main/java/com/cherrish/android/core/common/extension/FlowExt.ktapp/src/main/java/com/cherrish/android/core/network/NetworkModule.ktapp/src/main/java/com/cherrish/android/core/common/extension/ModifierExt.ktapp/src/main/java/com/cherrish/android/data/remote/datasourceimpl/DummyDataSourceImpl.ktapp/src/androidTest/java/com/cherrish/android/ExampleInstrumentedTest.ktapp/src/main/java/com/cherrish/android/data/remote/dto/response/DummyResponseDto.ktapp/src/main/java/com/cherrish/android/presentation/main/MainTab.ktapp/src/main/java/com/cherrish/android/presentation/mypage/navigation/MyPageNavigator.ktapp/src/main/java/com/cherrish/android/core/network/BaseResponse.ktapp/src/main/java/com/cherrish/android/core/local/di/DataStoreModule.ktapp/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.ktapp/src/main/java/com/cherrish/android/data/repositoryimpl/DummyRepositoryImpl.ktapp/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.ktapp/src/main/java/com/cherrish/android/presentation/main/MainAppState.ktapp/src/main/java/com/cherrish/android/data/remote/datasource/DummyDataSource.kt
🧬 Code graph analysis (7)
app/src/main/java/com/cherrish/android/presentation/challenge/navigation/ChallengeNavigator.kt (2)
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
navigate(61-79)app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt (1)
ChallengeRoute(9-14)
app/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.kt (2)
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
navigate(61-79)app/src/main/java/com/cherrish/android/presentation/calendar/CalendarScreen.kt (1)
CalendarRoute(9-14)
app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt (5)
app/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.kt (1)
MainBottomBar(35-70)app/src/main/java/com/cherrish/android/presentation/home/navigation/HomeNavigator.kt (1)
homeNavGraph(24-32)app/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.kt (1)
calendarNavGraph(24-32)app/src/main/java/com/cherrish/android/presentation/challenge/navigation/ChallengeNavigator.kt (1)
challengeNavGraph(24-32)app/src/main/java/com/cherrish/android/presentation/mypage/navigation/MyPageNavigator.kt (1)
myPageNavGraph(24-32)
app/src/main/java/com/cherrish/android/presentation/home/navigation/HomeNavigator.kt (2)
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
navigate(61-79)app/src/main/java/com/cherrish/android/presentation/home/HomeScreen.kt (1)
HomeRoute(9-14)
app/src/main/java/com/cherrish/android/presentation/mypage/navigation/MyPageNavigator.kt (2)
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
navigate(61-79)app/src/main/java/com/cherrish/android/presentation/mypage/MyPageScreen.kt (1)
MyPageRoute(9-14)
app/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.kt (1)
app/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.kt (1)
CherrishTheme(35-56)
app/src/main/java/com/cherrish/android/data/repositoryimpl/DummyRepositoryImpl.kt (1)
app/src/main/java/com/cherrish/android/core/util/SuspendRunCatching.kt (1)
suspendRunCatching(8-19)
⏰ 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: Build Check
🔇 Additional comments (58)
app/proguard-rules.pro (1)
1-21: 표준 ProGuard 템플릿 파일로 적절합니다.초기 프로젝트 세팅 단계에서 기본 템플릿을 사용하는 것은 적절합니다. 현재 모든 규칙이 주석 처리되어 있으며, 향후 릴리즈 빌드 및 코드 난독화가 필요할 때 실제 ProGuard/R8 규칙을 추가하면 됩니다.
app/src/main/java/com/cherrish/android/core/common/extension/StateFlowExt.kt (1)
7-17: LGTM! 상태 업데이트 패턴이 올바르게 구현되었습니다.이 확장 함수는 StateFlow 기반 상태 관리의 best practice를 잘 따르고 있습니다:
inline과crossinline키워드의 적절한 사용으로 성능 최적화MutableStateFlow.update를 통한 원자적(atomic) 업데이트 보장- Success 상태일 때만 변환을 적용하고 다른 상태는 보존하는 안전한 로직
- 불변성을 유지하는
copy()사용단방향 데이터 흐름 패턴과 Compose의 상태 관리 원칙에 부합합니다.
app/src/main/java/com/cherrish/android/core/local/di/DataStoreModule.kt (2)
15-17: DataStore 설정이 적절합니다.private 확장 속성을 사용하여 DataStore를 생성하는 방식이 올바르며, 파일명 "cherrish_preferences"도 명확합니다.
23-27: DataStore 제공 메서드가 올바르게 구현되었습니다.
@Singleton과@ApplicationContext를 적절히 사용하여 DataStore 인스턴스를 제공하고 있습니다.app/.gitignore (1)
1-1: LGTM!표준적인 안드로이드 프로젝트 .gitignore 설정입니다. Gradle 빌드 산출물이 버전 관리에서 적절히 제외됩니다.
.github/CODEOWNERS (1)
1-1: LGTM!초기 설정 단계에서 모든 경로를 팀에 할당하는 것은 적절합니다. 프로젝트가 성장함에 따라 나중에 모듈별(예: feature, data, ui) 또는 담당자별로 규칙을 세분화할 수 있습니다.
.run/Cherrish [ktLintFormat].run.xml (1)
1-24: LGTM!ktLintFormat 작업을 위한 IDE 실행 구성이 올바르게 설정되었습니다. 이는 개발자가 IDE에서 직접 코드 포맷팅을 실행할 수 있게 하여 개발 경험을 향상시킵니다.
app/src/main/res/xml/backup_rules.xml (1)
8-13: 초기 세팅으로 적절합니다.백업 규칙이 주석 처리된 예제로만 구성되어 있습니다. 프로젝트 초기 세팅 단계에서는 적절하지만, 향후 실제 앱 데이터 백업 정책을 정의할 때 이 파일을 업데이트해야 합니다. SharedPreferences나 민감한 데이터를 포함/제외할 규칙을 명시적으로 정의하는 것을 고려하세요.
gradle/wrapper/gradle-wrapper.properties (1)
3-3: Gradle 9.0.0 호환성 확인 완료 - 문제 없음프로젝트의 플러그인 버전을 확인했습니다:
- Android Gradle Plugin (AGP): 8.13.2
- Kotlin: 2.1.20
- Hilt: 2.56.2
Gradle 9.0.0은 AGP 8.4 이상을 요구하며, 현재 프로젝트의 AGP 8.13.2는 이 요구사항을 충족합니다. 따라서 Gradle 9.0.0과의 호환성 문제는 없습니다.
.run/Cherrish [ktLintCheck].run.xml (1)
1-24: ktLintCheck 실행 구성이 올바르게 설정되었습니다.IDE에서 ktLintCheck를 실행하기 위한 표준 Gradle 실행 구성이 적절하게 구성되어 있습니다. 디버그 옵션도 활성화되어 있어 필요시 디버깅이 가능합니다.
gradle.properties (1)
1-23: Gradle 프로젝트 설정이 적절합니다.프로젝트 전역 Gradle 설정이 Android 프로젝트에 적합하게 구성되어 있습니다:
- JVM 메모리 할당 (2GB)이 적절함
- AndroidX 사용 활성화
- Kotlin 공식 코드 스타일 적용
- nonTransitiveRClass로 R 클래스 크기 최적화
.gitignore (1)
238-238: Gradle wrapper jar 명시적 포함은 모범 사례입니다.
!/gradle/wrapper/gradle-wrapper.jar패턴으로 Gradle wrapper를 버전 관리에 포함시키는 것은 프로젝트의 재현 가능한 빌드를 보장하는 모범 사례입니다.app/src/main/java/com/cherrish/android/core/common/extension/RunCatchingExt.kt (1)
5-10: Result 확장 함수가 올바르게 구현되었습니다.이 확장 함수는 실패한 Result를 로깅하면서도 원본 Result를 보존합니다:
suspend inline+crossinline의 올바른 사용onFailure를 통한 체이닝으로 원본 Result 반환- Timber를 활용한 중앙화된 에러 로깅
- 명확한 네이밍으로 함수 의도 표현
.coderabbit.yaml (1)
1-32: CodeRabbit 설정이 프로젝트에 적합하게 구성되었습니다.설정이 Android Kotlin 프로젝트에 맞게 적절히 구성되었습니다:
- 한국어 리뷰 활성화 (line 1)
- 자동 리뷰 및 고수준 요약 포함 (lines 5-9)
- Kotlin 파일에 대한 명확한 리뷰 가이드라인 제공 (lines 18-23): Jetpack Compose, ViewModel, 상태 관리 패턴 중심
- 빌드 산출물 및 이미지 파일 제외 (lines 26-32)
모든 설정이 스키마에 부합하며 팀의 리뷰 프로세스를 효과적으로 지원합니다.
gradlew.bat (1)
1-94: LGTM!Gradle에서 자동 생성된 표준 Windows 배치 스크립트입니다. Java 감지, 에러 처리, 실행 로직이 올바르게 구성되어 있습니다.
app/src/main/res/drawable/ic_launcher_foreground.xml (1)
1-30: LGTM!표준 Android 적응형 아이콘 foreground 벡터 드로어블입니다. 108dp 크기와 그라데이션 그림자가 올바르게 설정되어 있습니다.
app/src/androidTest/java/com/cherrish/android/ExampleInstrumentedTest.kt (1)
1-24: LGTM!표준 Android Instrumented 테스트 템플릿입니다. 패키지명 검증이 올바르게 설정되어 있습니다.
gradlew (1)
1-251: LGTM!Gradle에서 자동 생성된 표준 POSIX 셸 스크립트입니다. 다양한 운영체제(Cygwin, MSYS, Darwin 등)에 대한 처리가 올바르게 구성되어 있습니다.
app/src/main/res/values/colors.xml (1)
3-9: 미사용 템플릿 색상 제거 또는 브랜드 컬러로 교체가 필요합니다.이 색상들은 Android Studio 기본 템플릿이며 프로젝트 전체에서 참조되지 않습니다.
- 프로젝트는 Compose 기반이므로 XML 레이아웃이 없고, 이 XML 색상들이 사용되는 곳이 없습니다.
- 프로젝트 색상은
Color.kt에서 관리되므로 (Purple80, PurpleGrey80, Pink80 등), 이 미사용 템플릿 색상들은 제거하거나 Cherrish 브랜드 컬러로 교체하는 것을 권장합니다.Likely an incorrect or invalid review comment.
app/src/main/res/mipmap-anydpi/ic_launcher.xml (1)
1-6: LGTM! Adaptive icon 구조가 올바릅니다.Android 13+의 themed icons를 지원하는 monochrome 레이어가 포함되어 있습니다. 표준 adaptive icon 패턴을 따르고 있습니다.
app/src/main/java/com/cherrish/android/data/di/ServiceModule.kt (2)
11-13: LGTM! Hilt 모듈 구조가 올바릅니다.SingletonComponent에 설치된 표준 Hilt 모듈 패턴을 따르고 있습니다.
14-18: LGTM! Retrofit 서비스 제공 패턴이 올바릅니다.DummyService 제공 로직이 표준 Retrofit + Hilt 패턴을 따르고 있습니다. 초기 설정 단계에서 Dummy 서비스는 적절하며, 실제 서비스 구현 시 유사한 패턴을 따르면 됩니다.
app/src/main/java/com/cherrish/android/core/common/navigation/Route.kt (1)
1-3: LGTM! 네비게이션 시스템의 기본 계약이 적절합니다.Route 인터페이스가 네비게이션 계층의 루트 타입으로 명확하게 정의되었습니다. 마커 인터페이스 패턴을 사용하여 타입 안정성을 제공하면서도 확장성을 유지하고 있습니다.
app/src/main/res/values/themes.xml (1)
1-4: LGTM! Compose 앱을 위한 기본 테마 설정이 적절합니다.NoActionBar를 사용하여 Compose 기반 UI와 호환되도록 설정했습니다. 실제 앱 테마링은 CherrishTheme Compose 함수에서 처리될 것으로 보이며, 이 XML 테마는 시스템 UI를 위한 기본 설정으로 적절합니다.
app/src/main/res/drawable/ic_launcher_background.xml (1)
1-170: LGTM!적응형 아이콘 배경을 위한 표준 Android 벡터 드로어블입니다. 108dp 크기와 뷰포트가 적응형 아이콘 가이드라인에 맞게 올바르게 설정되어 있습니다.
app/src/main/res/xml/data_extraction_rules.xml (1)
1-19: LGTM!Android 12+ 백업/복원을 위한 표준 데이터 추출 규칙 템플릿입니다. 프로젝트 초기 설정 단계에서는 적절하며, 추후 민감한 데이터가 추가되면 백업 규칙을 구체화하시면 됩니다.
app/src/main/res/mipmap-anydpi/ic_launcher_round.xml (1)
1-6: LGTM!적응형 아이콘 구성이 올바릅니다. 모노크롬 레이어가 포그라운드를 사용하여 Android 13+ 테마 아이콘을 지원합니다.
app/src/main/java/com/cherrish/android/data/repository/DummyRepository.kt (1)
1-7: LGTM!Repository 인터페이스가 Google 권장 Android 아키텍처 패턴을 잘 따르고 있습니다.
Result<T>타입을 사용한 에러 핸들링과 suspend 함수 사용이 적절합니다.app/src/main/java/com/cherrish/android/presentation/mypage/MyPageScreen.kt (1)
16-25: LGTM!플레이스홀더 스크린으로서 적절한 구현입니다. Route-to-Screen 패턴이 프로젝트 내 다른 스크린들과 일관되게 적용되어 있습니다.
app/src/main/java/com/cherrish/android/data/di/RepositoryModule.kt (1)
1-19: LGTM!Hilt DI 모듈이 올바르게 구성되어 있습니다.
@Binds를 사용한 인터페이스-구현체 바인딩과@Singleton스코프 적용이 적절합니다.app/src/main/java/com/cherrish/android/core/util/SuspendRunCatching.kt (1)
8-19: 코루틴 예외 처리가 올바르게 구현되었습니다.suspend 함수의 예외 처리 패턴이 정확합니다:
CancellationException재발생으로 코루틴 취소를 보존TimeoutCancellationException을 실패로 처리하여 타임아웃을 명시적으로 핸들링- 다른 예외 발생 시
ensureActive()호출로 코루틴 상태 검증build.gradle.kts (1)
1-8: 멀티모듈 프로젝트 설정이 적절합니다.루트 build.gradle.kts의 플러그인 선언이 올바릅니다:
apply false로 하위 모듈에서 선택적 적용 가능- Version catalog 활용으로 의존성 버전 중앙 관리
- 필요한 핵심 플러그인(Android, Kotlin, Compose, Hilt, KSP) 포함
app/src/main/java/com/cherrish/android/CherrishApplication.kt (1)
8-25: Application 초기화가 올바르게 구성되었습니다.구현이 안드로이드 모범 사례를 따릅니다:
@HiltAndroidApp으로 Hilt DI 설정 완료- Timber 로깅은 디버그 빌드에만 활성화
- Day mode 강제 설정으로 일관된 UI 제공 (의도적인 설계로 보임)
app/src/main/java/com/cherrish/android/data/remote/datasource/DummyDataSource.kt (1)
6-8: 데이터 소스 인터페이스가 적절합니다.초기 프로젝트 세팅을 위한 더미 데이터 소스 계약이 명확하게 정의되어 있습니다.
app/src/main/java/com/cherrish/android/presentation/main/MainScreen.kt (1)
17-51: Compose 아키텍처와 상태 관리가 잘 구현되었습니다.MainScreen의 구조가 권장 패턴을 따릅니다:
collectAsStateWithLifecycle로 생명주기 안전한 상태 수집MainAppState를 통한 단방향 데이터 흐름- Scaffold와 NavHost를 활용한 명확한 네비게이션 구조
- 탭 네비게이션에 적합하게 전환 애니메이션 비활성화
app/src/main/java/com/cherrish/android/presentation/main/MainActivity.kt (1)
10-23: 깔끔한 Activity 구조입니다!
@AndroidEntryPoint를 통한 Hilt 통합,enableEdgeToEdge()를 통한 엣지-투-엣지 UI 설정, 그리고 Compose 기반 UI 구성이 모두 적절하게 구현되어 있습니다.rememberMainAppState()를 통해 상태가 올바르게 remember되어 불필요한 recomposition을 방지하고 있습니다.app/src/main/java/com/cherrish/android/data/di/DataSourceModule.kt (1)
11-19: 표준적인 Hilt DI 모듈 구성입니다.
@Binds를 사용한 인터페이스 바인딩과@Singleton스코프 적용이 올바르게 구현되어 있습니다.SingletonComponent에 설치되어 스코프 일관성도 유지되고 있습니다.app/src/main/java/com/cherrish/android/data/model/DummyModel.kt (1)
5-15: 명확한 데이터 모델 구조입니다.
DummyModel데이터 클래스와toModel()확장 함수를 통한 DTO-to-Model 매핑이 간결하게 구현되어 있습니다. 초기 더미 구현으로 적절합니다.app/src/main/java/com/cherrish/android/presentation/challenge/ChallengeScreen.kt (1)
9-25: Route/Screen 분리 패턴이 잘 적용되었습니다.Public
ChallengeRoute와 privateChallengeScreen으로 분리하여 테스트 가능성과 캡슐화를 확보했습니다.modifier와paddingValues가 적절히 적용되어 있으며, 초기 플레이스홀더 구현으로 적합합니다.app/src/main/java/com/cherrish/android/presentation/challenge/navigation/ChallengeNavigator.kt (1)
12-32: 타입 안전 네비게이션 패턴이 일관되게 적용되었습니다.
@Serializabledata object를 활용한 타입 안전 네비게이션 구현이 우수합니다.navigateToChallenge확장 함수와challengeNavGraph빌더가 다른 탭 네비게이터들(Home, Calendar, MyPage)과 일관된 패턴을 따르고 있어 유지보수성이 높습니다.gradle/libs.versions.toml (2)
108-167: LGTM!번들 구성이 잘 정리되어 있습니다. 관련 라이브러리들이 논리적으로 그룹화되어 있어 의존성 관리가 용이합니다.
3-5: 모든 버전이 유효합니다각 버전이 실제로 존재합니다:
compileSdk = "36": Android 16으로 2025년 3월에 platform stable 상태로 출시됨agp = "8.13.2": Android Gradle Plugin 8.13 라인의 정식 릴리스kotlin = "2.1.20": 2025년 3월 20일 릴리스retrofit = "3.0.0": 2025년 5월 15일 릴리스다만 Retrofit 3.0.0은 OkHttp 4.12로 테스트되었으나, 프로젝트에서는 OkHttp 5.3.2를 사용하고 있습니다. 호환성 확인이 필요할 수 있습니다.
Likely an incorrect or invalid review comment.
app/src/main/java/com/cherrish/android/data/remote/datasourceimpl/DummyDataSourceImpl.kt (1)
9-14: LGTM!DataSource 구현이 깔끔하고 표준 패턴을 잘 따르고 있습니다. Hilt DI를 위한
@Injectconstructor 사용과 인터페이스 위임이 적절합니다.app/src/main/java/com/cherrish/android/presentation/home/navigation/HomeNavigator.kt (1)
12-32: LGTM!Type-safe navigation 패턴을 잘 따르고 있습니다.
@Serializabledata object와 NavController/NavGraphBuilder 확장 함수 구성이 다른 탭 네비게이터들과 일관성 있게 구현되어 있습니다.app/src/main/java/com/cherrish/android/presentation/calendar/navigation/CalendarNavigator.kt (1)
12-32: LGTM!HomeNavigator와 동일한 패턴으로 일관성 있게 구현되어 있습니다. 네비게이션 구조가 유지보수하기 좋게 설계되었습니다.
app/src/main/java/com/cherrish/android/core/designsystem/theme/Type.kt (1)
10-34: LGTM!Material 3 Typography의 기본 설정이 적절합니다. 주석 처리된 템플릿은 추후 커스터마이징 시 유용한 가이드가 됩니다.
app/src/main/AndroidManifest.xml (1)
1-30: LGTM!Manifest 설정이 완벽합니다. 인터넷 권한, 백업 규칙, 런처 액티비티 등 초기 설정에 필요한 모든 요소가 적절하게 구성되어 있습니다.
app/build.gradle.kts (2)
40-53: Release 빌드에서 코드 난독화 비활성화 확인Release 빌드 타입에서
isMinifyEnabled = false로 설정되어 있습니다. 초기 설정 단계에서는 문제없지만, 프로덕션 배포 전에true로 변경하여 코드 난독화 및 최적화를 활성화해야 합니다.
103-114: ktlint 설정이 적절합니다ktlint 구성이 잘 되어 있으며, 테스트 디렉토리를 제외하는 것도 합리적입니다. Android 모드, 디버그, 컬러 출력 등의 옵션이 개발 환경에 적합합니다.
app/src/main/java/com/cherrish/android/presentation/mypage/navigation/MyPageNavigator.kt (2)
12-13: Serializable 타입 안전 네비게이션 적용이 우수합니다
@Serializable data object를 사용한 타입 안전 네비게이션 패턴이 잘 적용되어 있습니다. Kotlin Serialization 기반의 최신 Compose Navigation API를 올바르게 활용하고 있습니다.
15-32: 네비게이션 구조가 일관성 있게 구현되었습니다
navigateToMyPage확장 함수와myPageNavGraph빌더 함수가 다른 탭 네비게이터들과 동일한 패턴을 따르고 있어 코드베이스의 일관성이 유지됩니다. PaddingValues를 전달하여 시스템 UI와의 충돌을 방지하는 것도 적절합니다.app/src/main/java/com/cherrish/android/core/designsystem/theme/Theme.kt (1)
42-49: 색상 스키마 선택 로직이 올바르게 구현되었습니다동적 색상 지원과 다크/라이트 모드 전환 로직이 Material3 가이드라인에 맞게 잘 구현되어 있습니다. Build.VERSION 체크도 적절합니다.
app/src/main/java/com/cherrish/android/core/network/NetworkModule.kt (2)
29-34: JSON 설정이 적절합니다
ignoreUnknownKeys = true설정으로 API 변경에 유연하게 대응할 수 있고,prettyPrint = true는 디버깅에 유용합니다. Kotlinx Serialization 활용이 잘 되어 있습니다.
43-60: 확장 함수가 이미 정의되어 있습니다.isJsonObject()와isJsonArray()확장 함수는app/src/main/java/com/cherrish/android/core/network/JsonStringExt.kt에서 정의되어 있으며, NetworkModule.kt과 동일한 패키지(com.cherrish.android.core.network)에 있으므로 명시적 import 없이 자동으로 접근 가능합니다. 현재 코드는 정상적으로 작동합니다.Likely an incorrect or invalid review comment.
app/src/main/java/com/cherrish/android/presentation/main/component/MainBottomBar.kt (3)
36-41: ImmutableList 사용으로 불필요한 리컴포지션 방지
ImmutableList<MainTab>타입을 사용하여 Compose가 리스트 변경을 효율적으로 추적할 수 있도록 했습니다. 이는 리컴포지션 최적화를 위한 모범 사례입니다.
58-66: key()를 사용한 안정적인 컴포지션 보장
key(tab.route)를 사용하여 각 탭 아이템에 안정적인 식별자를 부여했습니다. 이는 리스트 재정렬 시 불필요한 리컴포지션을 방지하고 애니메이션을 안정적으로 유지하는 올바른 패턴입니다.
42-46: AnimatedVisibility 전환 효과가 적절합니다fadeIn/fadeOut과 slideIn/slideOut을 조합하여 부드러운 바텀바 표시/숨김 애니메이션을 구현했습니다. UX 측면에서 자연스러운 전환 효과입니다.
app/src/main/java/com/cherrish/android/presentation/main/MainAppState.kt (1)
82-91: rememberMainAppState 구현이 우수합니다.Composable에서 상태를 안전하게 기억하는 표준 패턴을 정확히 따르고 있습니다.
remember의 키로navController와coroutineScope를 지정하여 불필요한 재생성을 방지하고 있으며, 기본값 제공으로 사용성도 좋습니다.
hyeminililo
left a comment
There was a problem hiding this comment.
기초 세팅 정말 ,,, 엄청나게 잘하셨네요 ,, 세팅하신 코드를 보면서 열심히 공부를 더욱 해보겠습니다 !! 너무 수고하셨습니다 😭🥹
| import androidx.compose.ui.unit.dp | ||
|
|
||
| @Composable | ||
| inline fun Modifier.noRippleClickable( |
There was a problem hiding this comment.
Modifier를 이렇게 따로 선언해서 사용하는 이유가 있을까요 ? 궁금합니다 ! 그리고 inline으로 선언한 이유도 궁금합니다 :)
There was a problem hiding this comment.
먼저 Modifier를 확장 함수로 따로 선언하는 이유는 재사용성과 가독성 때문입니다! ripple 효과 없는 클릭 기능은 앱 전체에서 자주 사용되는 패턴인데, 매번 clickable에 indication null과 MutableInteractionSource를 기억해서 작성하는 건 번거롭고 실수하기 쉽죠. 이렇게 확장 함수로 만들어두면 필요한 곳에서 간단히 Modifier.noRippleClickable { }로 호출할 수 있어서 코드가 훨씬 깔끔해지고 의도도 명확해집니다. 또한 나중에 로직을 수정해야 할 때도 한 곳만 고치면 되니까 유지보수 측면에서도 유리해요.
그리고 inline으로 선언한 이유는 성능 최적화 때문입니다. inline을 사용하면 함수 호출 시 람다가 객체로 생성되는 대신, 호출 지점에 코드가 직접 삽입돼요. UI 관련 코드는 리컴포지션 때마다 자주 실행되는데 특히 LazyColumn같은 리스트에서 각 아이템마다 이 Modifier를 사용한다고 생각해보면 람다 객체 생성 오버헤드를 줄이는 게 꽤 중요하답니다 ㅎㅎ
There was a problem hiding this comment.
항상 Modifier가 중요하다고 들어서 공부를 해봤는데 아예 이렇게 따로 관리를 하는게 오버헤드가 훨씬 줄어드는 군요 ,,!! 감사합니다 😊
| suspend inline fun <T> Result<T>.onLogFailure( | ||
| crossinline action: suspend (exception: Throwable) -> Unit | ||
| ): Result<T> = onFailure { e -> | ||
| Timber.e(e) |
There was a problem hiding this comment.
저번 합세에서도 log를 활용하는것보다 Timber를 사용하는 것을 추천했는데 아직 이유를 모르겠습니다.,. 궁금합니다 😭
There was a problem hiding this comment.
안드로이드 기본 Log 클래스는 몇 가지 불편한 점들이 있어요. 가장 큰 문제는 TAG를 매번 수동으로 관리해야 한다는 거예요. Log.d(TAG, "message") 이런 식으로 쓰려면 클래스마다 private const val TAG = "MyActivity" 같은 걸 선언해야 하는데 이게 귀찮을 뿐만 아니라 클래스 이름이 바뀌면 TAG도 같이 수정해야 해서 실수하기 쉽죠. 리팩토링할 때도 놓치기 쉽구용.
Timber는 이런 문제를 자동으로 해결해줘요! Timber.d("message")만 쓰면 Timber가 알아서 호출한 클래스 이름을 TAG로 사용해줍니다. 코드도 더 간결해지고 실수할 여지도 없어지죠. 그리고 더 중요한 건 릴리즈 빌드 관리예요. 일반 Log는 릴리즈 빌드에서도 그대로 출력되기 때문에 ProGuard 설정으로 제거하거나 직접 BuildConfig.DEBUG로 감싸야 하는데 이것도 매번 하기 번거롭고 빼먹기 쉬워요. Timber는 Application 클래스에서 디버그 모드일 때만 Timber.plant(Timber.DebugTree())로 초기화하면 릴리즈 빌드에서는 자동으로 모든 로그가 무시돼요. 별도 설정 없이 안전하게 로그를 관리할 수 있는 거죠 ㅎㅎ
There was a problem hiding this comment.
릴리즈 되었을 때는 그럼 로그가 안 보이군요 !! 너무 신기해요 감사합니다 !!
| @@ -0,0 +1,13 @@ | |||
| package com.cherrish.android.core.common.state | |||
|
|
|||
| sealed interface UiState<out T> { | |||
There was a problem hiding this comment.
out은 찾아보니까 이 제네릭 타입을 결과로만 사용하려고 한다고 나오는데 맞을까요 ?? 정확히 조금 더 궁금합니다 !!
There was a problem hiding this comment.
네 맞아용! out 키워드는 공변성을 나타내는데, 쉽게 말하면 이 제네릭 타입 T를 생산만 하고 소비하지 않는다는 의미예요. 코드에서 보면 Success 클래스가 val data: T처럼 T를 결과로만 내보내고 있죠. 만약 fun setData(data: T) 같은 함수를 만들려고 하면 컴파일 에러가 나요. T를 파라미터로 받아서 소비하는 건 허용되지 않거든요.
그럼 왜 이렇게 할까요? 바로 타입 안정성을 유지하면서 유연한 타입 변환을 가능하게 하기 위해서예요. out 덕분에 UiState<String>을 UiState<Any>로 취급할 수 있어요. String은 Any의 하위 타입이니까 이런 변환이 안전하게 가능한 거죠. 이걸 공변성이라고 해요. 만약 out이 없으면 제네릭 타입이 정확히 일치해야만 하고 이런 유연한 변환이 불가능해요. 예를 들어 fun handleState(state: UiState<Any>) 같은 함수가 있을 때 User 타입의 UiState<User>를 넘길 수 있어요. out 덕분에 User가 Any의 하위 타입이면 자동으로 변환되는 거죠. 이게 없으면 타입별로 함수를 따로 만들어야 하는데 out 덕분에 하나의 함수로 모든 타입을 처리할 수 있답니다 ㅎㅎㅎ 결국 out은 이 타입은 안전하게 읽기만 할 거니까 더 넓은 타입으로 변환해도 괜찮다고 컴파일러에게 보장해주는 키워드예요. 그래서 UiState처럼 데이터를 담아서 전달만 하는 용도로 사용되는 타입에 딱 맞는 거죠!
There was a problem hiding this comment.
아 객체 타입을 한 번에 읽어줄 수 있는 역할을 하는 것이군요 ,,!! 감사합니다 !!
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class DummyResponseDto( |
There was a problem hiding this comment.
이렇게 더미로 넣어두는 방법이 있군요 ,, 이러면 확실히 세미나에서 배운대로 구조화를 하는게 더욱 효과적일 것 같네요 ,,! 덕분에 공부한번 더 하게 됩니당 ,,! !
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
.github/workflows/pr_checker.yml (2)
1-6: 동시성 제어 추가를 고려해보세요.워크플로우 기본 설정은 잘 구성되어 있습니다. 다만 동일한 PR에 대해 여러 워크플로우가 동시에 실행될 수 있어 CI 리소스가 낭비될 수 있습니다.
🔎 동시성 제어 추가 제안
name: Cherrish CI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: pull_request: branches: [ develop ]이렇게 하면 동일한 PR에 새로운 커밋이 푸시될 때 이전 실행 중인 워크플로우가 취소됩니다.
50-83: 중복된 설정 단계를 리팩토링하는 것을 권장합니다.빌드 작업이 정상적으로 구성되어 있으나, lint 작업과 설정 단계가 거의 동일하게 중복되어 있습니다 (lines 55-80과 lines 13-38).
🔎 중복 제거 방법 제안
방법 1: Composite Action 생성 (권장)
.github/actions/setup-android-env/action.yml파일을 생성하여 공통 설정을 재사용:name: 'Setup Android Environment' description: 'Setup Gradle, JDK, Android SDK and local.properties' runs: using: "composite" steps: - name: Setup Gradle uses: gradle/actions/setup-gradle@v4 - name: Set up JDK 17 uses: actions/setup-java@v5 with: java-version: 17 distribution: 'temurin' - name: Set up Android SDK uses: android-actions/setup-android@v3 - name: Create local.properties shell: bash env: DEBUG_BASE_URL: ${{ env.DEBUG_BASE_URL }} RELEASE_BASE_URL: ${{ env.RELEASE_BASE_URL }} run: | echo "sdk.dir=$ANDROID_SDK_ROOT" > local.properties echo "debug.base.url=\"${DEBUG_BASE_URL}\"" >> local.properties echo "release.base.url=\"${RELEASE_BASE_URL}\"" >> local.properties - name: Grant permission to gradlew shell: bash run: chmod +x ./gradlew그 다음 워크플로우에서 사용:
jobs: lint: name: PR Lint Check runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v6 - name: Setup Android Environment uses: ./.github/actions/setup-android-env env: DEBUG_BASE_URL: ${{ secrets.DEBUG_BASE_URL }} RELEASE_BASE_URL: ${{ secrets.RELEASE_BASE_URL }} - name: Run ktlint run: ./gradlew ktlintCheck # ... 나머지 단계방법 2: build 작업이 lint 작업에 의존하도록 설정
build: name: PR Build Check runs-on: ubuntu-latest + needs: lintlint가 실패하면 build가 실행되지 않아 리소스를 절약할 수 있습니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
.github/workflows/pr_checker.ymlgradle.properties
🚧 Files skipped from review as they are similar to previous changes (1)
- gradle.properties
⏰ 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). (2)
- GitHub Check: PR Lint Check
- GitHub Check: PR Build Check
🔇 Additional comments (1)
.github/workflows/pr_checker.yml (1)
8-49: Lint 작업이 잘 구성되어 있습니다.ktlint 검사 및 리포트 업로드 설정이 적절합니다. 최신 액션 버전을 사용하고 있으며, setup-gradle@v4가 자동으로 캐싱을 처리합니다.
Related issue 🛠
Work Description ✏️
Screenshot 📸
Uncompleted Tasks 😅
To Reviewers 📢
프로젝트 초기 세팅을 진행했습니다!
구조나 패키징, 라이브러리 구성 위주로 봐주시면 좋을 것 같아요.
더 좋은 방법이나 개선 아이디어가 떠오르시면 자유롭게 리뷰 남겨주세요!
질문도 환영입니다!
Summary by CodeRabbit
새로운 기능
Chores
Tests
문서/로컬라이제이션
✏️ Tip: You can customize this high-level summary in your review settings.