-
Notifications
You must be signed in to change notification settings - Fork 69
[만들면서 배우는 DI 5단계] 오이 미션 제출합니다. #228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: cucumber99
Are you sure you want to change the base?
Conversation
Summary by CodeRabbit릴리스 노트
Walkthrough이 PR은 커스텀 DI 프레임워크에서 Hilt 기반 의존성 주입으로의 전체 마이그레이션을 수행합니다. 변경 사항에는 Hilt 플러그인 및 의존성 추가, @HiltAndroidApp 및 @androidentrypoint 어노테이션 적용, 커스텀 @Inject에서 표준 javax.inject.Inject로의 변경, CartRepositoryModule, DatabaseModule, ProductRepositoryModule 등의 새로운 Hilt 모듈 생성, 리포지토리 인스턴스에 대한 @HiltRoomDB 및 @HiltInMemory 한정자 도입, 액티비티 및 ViewModel에서 injectViewModel() 대신 viewModels()를 사용하도록 업데이트, 그리고 생성자 기반 주입으로의 전환이 포함됩니다. Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 분
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
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/woowacourse/shopping/App.kt (1)
9-18: 오래된 커스텀 DI 시스템 초기화 코드를 제거하세요.검증 결과, App.kt의 다음 라인들은 실제로 실행되지만 사용되지 않습니다:
- Line 15:
container.bind(type = Context::class, provider = { this })- Lines 16-17:
container.installModule(DatabaseModule),container.installModule(RepositoryModule)실제 의존성 주입은 모두 Hilt를 통해 이루어집니다:
- MainActivity와 CartActivity는
@AndroidEntryPoint와by viewModels()를 사용- MainViewModel과 CartViewModel은
@HiltViewModel어노테이션 적용- DIViewModelFactory 클래스는 정의되어 있지만 프로덕션 코드에서 호출되지 않음
README의 "기존 모듈과 테스트 코드를 삭제하지 않아도 된다"는 테스트 파일 보존을 의미합니다. App.kt의 런타임 초기화 코드는 제거해야 합니다.
Lines 15-17 삭제 후 Container 필드도 제거하세요.
🧹 Nitpick comments (18)
build.gradle.kts (1)
11-16: ktlint 플러그인 적용 방식 변경이 기능적으로 동일합니다.기존 코드와 동일한 기능을 수행하지만 스타일만 변경되었습니다. 특별한 이유가 없다면 이러한 스타일 변경은 선택적입니다.
app/src/main/java/woowacourse/shopping/hilt/Qualifiers.kt (2)
5-11: Qualifier 정의 적절합니다.BINARY 보존, 명확한 이름 모두 좋아요. 실제 사용처가 많아지지 않도록 모듈 경계에서만 쓰는 것을 권장합니다.
1-11: 사용 가이드 주석과 테스트 전략 명시 제안
- 각 Qualifier의 선택 기준(운영: Room, 테스트: InMemory 등)과 적용 계층을 KDoc으로 남기면 혼선이 줄어듭니다.
- 프로덕션 코드에서 @HiltRoomDB 같은 구현 선택을 제거하고, 테스트에서만 @TestInstallIn 으로 모듈 교체하는 접근도 검토해보세요. 그러면 ViewModel/Activity가 구현에 결합되지 않습니다.
테스트 교체 전략이 목표와 맞는지 확인해 주세요. 필요하면 관련 템플릿을 드리겠습니다.
app/build.gradle.kts (5)
5-5: kapt 설정 보완Hilt 사용 시 kapt의 오류 메시지 개선을 위해 correctErrorTypes를 켜두면 편합니다.
적용 예:
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.kapt) alias(libs.plugins.hilt.android) } +// kapt 설정 +kapt { + correctErrorTypes = true +}
80-84: Hilt 테스트 의존성 추가 제안 (VM 테스트 실패 대응)
- JVM/Robolectric: testImplementation("hilt-android-testing"), kaptTest("hilt-compiler")
- 계측 테스트: androidTestImplementation("hilt-android-testing"), kaptAndroidTest("hilt-compiler")
원하시면 JUnit4(HiltRule) 또는 Robolectric + @TestInstallIn 템플릿을 마련해 드립니다.
19-20: 계측 테스트에서 Hilt 사용 계획 여부 확인계측 테스트에서 @HiltAndroidTest를 쓸 예정이면 HiltTestRunner 설정이 필요합니다.
예시:
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + testInstrumentationRunner = "com.google.dagger.hilt.android.testing.HiltTestRunner"
48-51: 과도한 패키징 exclude 재검토META-INF/** 전체 제외는 라이선스/버전 메타데이터까지 제거합니다. 꼭 필요한 패턴만 명시하는 쪽을 권장합니다.
44-46: JUnit5 + Hilt 테스트 혼용시 주의Hilt의 공식 테스트 유틸은 JUnit4 Rule 기반입니다. 현재 Vintage 엔진을 추가하셨으니 JUnit4 테스트가 섞여도 실행되지만, 전략을 명확히 해두세요(모두 JUnit4 or Vintage로 혼합).
app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt (5)
16-22: @hiltviewmodel + 생성자 주입 전환은 적절합니다. 다만 구현 Qualifier 결합은 최소화 권장@HiltRoomDB가 ViewModel에 직접 새겨지면 구현에 결합됩니다. 기본 바인딩을 모듈에서 결정하고, 테스트에서만 @TestInstallIn으로 교체하면 VM은 인터페이스에만 의존할 수 있습니다.
원하시면 CartRepositoryModule 대체용 테스트 모듈 예시를 드릴게요.
26-28: Boolean LiveData로 단발성 이벤트 전달은 재발행/구성변경 이슈가 있습니다
- 구성 변경 시 토스트가 재표출될 수 있습니다.
- 여러 클릭 연속 처리/에러 케이스가 섞이면 상태 관리가 어려워집니다.
대안: Channel/SharedFlow(emit 단발), 또는 Event 래퍼 패턴을 권장합니다.현재 테스트 실패가 이 이벤트 처리와 연관 있는지 확인 부탁드립니다.
Also applies to: 29-34
29-34: 스레드 컨텍스트와 에러 핸들링 보완
- viewModelScope.launch 기본은 Main이지만, repo 내부에서 IO로 전환 후 복귀하지 않으면 setValue 호출이 메인 스레드가 아닐 수 있습니다. 안전하게 마지막 갱신만 Main에서 하거나 postValue 사용을 고려하세요.
- 예외 처리(try/catch)로 사용자 피드백/재시도 신호를 노출해 주세요.
36-38: 동기 API 호출 여부 점검getAllProducts()가 DB/IO 접근이라면 메인 스레드 블로킹 위험이 있습니다. suspend/Flow로 노출하고 수집은 viewModelScope에서 비동기로 처리하는 쪽을 권장합니다.
repo의 getAllProducts()가 메인-세이프인지 확인 부탁드립니다.
40-46: 매핑 위치 정리(선택사항)Product→CartProduct 변환은 확장 함수로 분리되어 있어 좋습니다. 공용으로 쓰인다면 domain/data 계층 유틸로 이동하여 재사용성을 높일 수 있습니다.
app/src/main/java/woowacourse/shopping/ui/MainActivity.kt (3)
67-75: Adapter 재생성 대신 재사용 권장products 변경 때마다 ProductAdapter를 새로 만들면 스크롤/애니메이션 상태가 초기화됩니다. Activity 필드로 보관해 재사용하고, DiffUtil(ListAdapter)로 submitList만 갱신해 보세요.
76-80: 단발성 토스트 이벤트 소비 방식 개선구성 변경 시 재표출을 막고 중복 클릭을 다루기 위해 SharedFlow/Channel 또는 Event 래퍼로 소비하고 나면 자동으로 소모되도록 바꾸는 것을 권장합니다.
63-66: 데이터 로딩 트리거 위치 검토(선택)onCreate에서 직접 트리거하기보다, ViewModel이 init에서 흐름을 시작하거나 UI가 Flow/Livedata를 관찰만 하도록 만들면 책임이 단순해집니다.
app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt (1)
20-21: DAO 범위(선택)DAO는 DB 싱글톤에서 파생되므로 별도 @singleton은 필수는 아닙니다. 필요 시 동일 스코프로 표시해도 무방합니다.
app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt (1)
20-27: 필드 주입 패턴에 대해 생각해보세요.현재
dateFormatter는 필드 주입으로,adapter는 lazy 초기화로 구현되어 있습니다. 이 패턴이 작동하는 이유는 무엇일까요?테스트를 작성할 때 필드 주입된 의존성을 어떻게 mock으로 교체할 수 있을지 고민해보시면 좋을 것 같습니다. Hilt는 테스트에서 의존성을 교체하기 위한 여러 방법을 제공하는데,
@TestInstallIn이나 custom test module 등을 찾아보시면 도움이 될 것 같습니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting
📒 Files selected for processing (17)
README.md(1 hunks)app/build.gradle.kts(2 hunks)app/src/main/java/woowacourse/shopping/App.kt(1 hunks)app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt(1 hunks)app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt(1 hunks)app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt(1 hunks)app/src/main/java/woowacourse/shopping/hilt/Qualifiers.kt(1 hunks)app/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.kt(1 hunks)app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt(1 hunks)app/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.kt(1 hunks)app/src/main/java/woowacourse/shopping/ui/MainActivity.kt(1 hunks)app/src/main/java/woowacourse/shopping/ui/MainViewModel.kt(1 hunks)app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt(1 hunks)app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt(1 hunks)app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt(1 hunks)build.gradle.kts(1 hunks)gradle/libs.versions.toml(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt
⚙️ CodeRabbit configuration file
**/*.kt: You MUST follow these guidelines:
- Do not provide complete solutions unless explicitly requested.
- Guide the programmer in problem-solving instead of providing direct answers.
- When asked about programming concepts (e.g., "What is a hook?"), give direct and clear explanations.
- Break problems into smaller, manageable steps to help think through them logically.
- Ask leading questions and provide hints instead of just telling the answer.
- Encourage independent debugging before offering suggestions.
- Refer to relevant documentation instead of providing ready-made solutions.
- Promote modular thinking—breaking problems into reusable components.
- Remind the programmer to reflect on lessons learned after solving an issue.
- If explicitly asked for code (e.g., "Give me the code"), then provide it.
Files:
app/src/main/java/woowacourse/shopping/App.ktapp/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.ktapp/src/main/java/woowacourse/shopping/ui/cart/CartActivity.ktapp/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.ktapp/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.ktapp/src/main/java/woowacourse/shopping/ui/MainActivity.ktapp/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.ktapp/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.ktapp/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.ktapp/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.ktapp/src/main/java/woowacourse/shopping/hilt/Qualifiers.ktapp/src/main/java/woowacourse/shopping/ui/MainViewModel.ktapp/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt
🔇 Additional comments (17)
app/src/main/java/woowacourse/shopping/data/repository/InMemoryCartRepository.kt (1)
5-9: LGTM! import 변경이 올바르게 적용되었습니다.커스텀 DI에서 표준 javax.inject.Inject로의 마이그레이션이 정확합니다. Hilt가 생성자 주입을 통해 이 Repository를 제공할 수 있게 되었습니다.
app/src/main/java/woowacourse/shopping/data/repository/ProductRepositoryImpl.kt (1)
5-9: LGTM! 표준 의존성 주입 어노테이션으로 마이그레이션 완료ProductRepositoryModule과 함께 Hilt DI 그래프에 올바르게 통합될 것입니다.
app/src/main/java/woowacourse/shopping/hilt/module/ProductRepositoryModule.kt (1)
11-17: LGTM! ProductRepository의 ViewModel 스코프 설정이 요구사항과 일치합니다.@BINDS를 사용한 인터페이스 바인딩과 @ViewModelScoped 스코프 설정이 4단계 요구사항(ProductRepository는 ViewModel LifeCycle 동안 유지)을 올바르게 충족합니다.
build.gradle.kts (1)
7-7: Hilt 플러그인 추가가 올바릅니다.프로젝트의 Hilt 마이그레이션을 지원하는 필수 설정입니다.
app/src/main/java/woowacourse/shopping/data/repository/DefaultCartRepository.kt (1)
7-13: LGTM! DAO 의존성을 포함한 생성자 주입이 올바릅니다.Hilt가 DatabaseModule을 통해 CartProductDao를 제공하여 이 Repository를 생성할 수 있습니다.
README.md (1)
56-57: 5단계 요구사항 문서화가 적절합니다.Hilt 마이그레이션 요구사항이 명확하게 기록되었습니다.
app/src/main/java/woowacourse/shopping/ui/cart/DateFormatter.kt (2)
12-17: DateFormatter의 스코프 선택에 대해 검토가 필요합니다.현재
@ActivityRetainedScoped를 사용하고 있는데, 이는 Configuration Changes(화면 회전 등)에도 인스턴스가 살아남는 스코프입니다.README의 4단계 필수 요구사항에는 "DateFormatter는 Activity LifeCycle 동안 유지"라고 되어 있고, 선택 요구사항에는 "Configuration Changes에도 살아남을 수 있도록 구현"이라고 되어 있습니다.
다음을 고려해보세요:
- 필수 요구사항만 만족하려면 어떤 스코프를 사용해야 할까요?
- 현재 구현은 어떤 요구사항을 만족하고 있나요?
- 선택 요구사항을 구현한 것이라면, 이것이 의도된 것인지 확인이 필요합니다.
18-24: Context 주입과 formatter 초기화 로직이 올바릅니다.@ApplicationContext를 사용하여 안전하게 Context를 주입받고, 생성자에서 SimpleDateFormat을 초기화하는 패턴이 적절합니다.
app/src/main/java/woowacourse/shopping/ui/MainActivity.kt (1)
17-20: Hilt 도입과 viewModels() 적용 👍@androidentrypoint + viewModels() 조합이 표준 패턴입니다.
app/build.gradle.kts (2)
56-56: :di 의존성은 여전히 필요함 — 제거 부적절검색 결과에 따르면
:di모듈이 여러 곳에서 활발히 사용 중입니다:
App.kt:DatabaseModule,RepositoryModule직접 참조 (Hilt 설정)RepositoryModule.kt:RoomDB,InMemory어노테이션 사용- 테스트 파일들:
RoomDB어노테이션 import이 의존성을 제거하면 컴파일 오류가 발생합니다. 현재 상태대로 유지하세요.
32-37: Java 21 타깃 호환성 확인 완료확인 결과, 프로젝트의 구성이 공식 지원됩니다:
- AGP 8.5.2: Java 21 타깃 공식 지원 ✓
- Kotlin 1.9.25: jvmTarget = "21" 공식 지원 (1.9.20부터 지원) ✓
- Gradle 8.9: Java 21 완전 지원 ✓
- 현재 환경: Java 17은 Java 21 바이트코드 생성에 충분함 ✓
빌드 JVM이 Java 21일 필요는 없습니다. Java 17에서 Java 21 바이트코드를 생성할 수 있으므로 현재 구성은 문제없습니다.
app/src/main/java/woowacourse/shopping/hilt/module/CartRepositoryModule.kt (1)
14-26: 모듈 구조가 잘 설계되었습니다!Qualifier를 사용해서 동일한 인터페이스에 대한 여러 구현체를 제공하는 방식이 적절합니다.
@Singleton스코프와SingletonComponent설치가 잘 매칭되어 있네요.gradle/libs.versions.toml (1)
26-27: Hilt 의존성 설정이 올바르게 추가되었습니다.필요한 Hilt 라이브러리와 플러그인이 모두 포함되어 있습니다.
Also applies to: 56-58, 66-66
app/src/main/java/woowacourse/shopping/ui/cart/CartActivity.kt (2)
15-18: Hilt 마이그레이션이 적절하게 수행되었습니다.
@AndroidEntryPoint와viewModels()delegate를 사용한 ViewModel 주입이 올바릅니다.
15-27: 테스트 실패 관련 힌트입니다.PR 설명에서 ViewModel 테스트가 실패한다고 하셨는데, Hilt를 사용하는 컴포넌트를 테스트할 때 몇 가지 고려사항이 있습니다:
- 테스트 클래스에 어떤 annotation이 필요할까요?
- Hilt가 제공하는 test rule이 있는지 찾아보셨나요?
- 테스트용 module을 어떻게 제공할 수 있을까요?
Hilt 공식 문서의 testing 섹션을 살펴보시면
@HiltAndroidTest,HiltAndroidRule, 그리고@UninstallModules/@TestInstallIn같은 유용한 도구들을 발견하실 수 있을 것입니다.app/src/main/java/woowacourse/shopping/ui/cart/CartViewModel.kt (2)
14-19: ViewModel의 Hilt 마이그레이션이 올바릅니다.
@HiltViewModel과 생성자 주입, 그리고@HiltRoomDBqualifier 사용이 모두 적절합니다.CartRepositoryModule에서 제공하는 바인딩과 잘 연결되어 있네요.
14-39: ViewModel 테스트 해결을 위한 가이드입니다.ViewModel 테스트가 실패한다고 하셨는데, 다음 질문들을 스스로에게 해보시면 도움이 될 것 같습니다:
@HiltViewModel로 annotate된 ViewModel을 테스트할 때, Hilt는 어떻게 의존성을 제공할까요?- 테스트에서
@HiltRoomDB로 qualified된CartRepository를 어떻게 mock이나 fake로 교체할 수 있을까요?- ViewModel의 경우 AndroidX Test에서 제공하는 특별한 방법이 있을까요? (힌트: InstantTaskExecutorRule과 관련이 있습니다)
Hilt 문서의 "Testing ViewModels" 섹션과 AndroidX의 "Testing Guide"를 함께 참고하시면 해답을 찾으실 수 있을 것입니다.
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| object DatabaseModule { | ||
| @Provides | ||
| fun provideShoppingDatabase( | ||
| @ApplicationContext context: Context, | ||
| ): ShoppingDatabase = ShoppingDatabase.getInstance(context) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Room DB는 @singleton으로 제공해야 합니다
여러 인스턴스 생성 시 캐시/트랜잭션 혼란과 자원 낭비가 발생합니다. @singleton을 추가해 주세요.
적용 예:
import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {
- @Provides
+ @Provides
+ @Singleton
fun provideShoppingDatabase(
@ApplicationContext context: Context,
): ShoppingDatabase = ShoppingDatabase.getInstance(context)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Module | |
| @InstallIn(SingletonComponent::class) | |
| object DatabaseModule { | |
| @Provides | |
| fun provideShoppingDatabase( | |
| @ApplicationContext context: Context, | |
| ): ShoppingDatabase = ShoppingDatabase.getInstance(context) | |
| import dagger.Module | |
| import dagger.Provides | |
| import dagger.hilt.InstallIn | |
| import dagger.hilt.components.SingletonComponent | |
| import javax.inject.Singleton | |
| import android.content.Context | |
| import dagger.hilt.android.qualifiers.ApplicationContext | |
| import woowacourse.shopping.data.database.ShoppingDatabase | |
| @Module | |
| @InstallIn(SingletonComponent::class) | |
| object DatabaseModule { | |
| @Provides | |
| @Singleton | |
| fun provideShoppingDatabase( | |
| @ApplicationContext context: Context, | |
| ): ShoppingDatabase = ShoppingDatabase.getInstance(context) | |
| } |
🤖 Prompt for AI Agents
In app/src/main/java/woowacourse/shopping/hilt/module/DatabaseModule.kt around
lines 12 to 19, the Room database provider is missing a @Singleton scope which
can cause multiple instances and resource/transaction issues; update the
provider by annotating the provideShoppingDatabase function with @Singleton so
Hilt creates a single ShoppingDatabase instance, and add the necessary import
for javax.inject.Singleton if not already present.
셀프 체크리스트
README.md에 기능 목록을 정리하고 명확히 기술하였는가?어떤 부분에 집중하여 리뷰해야 할까요?
@ActivityRetainedScoped와 같은 생명주기 관련 스코프 및@Inject어노테이션을 붙여서 의존성을 주입하는 방법의 차이를 아직 자세히 모르겠습니다. 어차피 모듈을 만들어도@Inject어노테이션을 붙여야 하는데...코드 리뷰 커뮤니케이션
📌 GitHub에서 PR에 댓글 남기는 방법
참고 자료
스크린샷