[만들면서 배우는 DI 5단계] 오이 미션 제출합니다.#228
[만들면서 배우는 DI 5단계] 오이 미션 제출합니다.#228cucumber99 wants to merge 11 commits intowoowacourse:cucumber99from
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.
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.
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에 댓글 남기는 방법
참고 자료
스크린샷