Skip to content

Conversation

@easyhooon
Copy link
Contributor

@easyhooon easyhooon commented Nov 21, 2025

🔗 관련 이슈

📙 작업 설명

  • 의존성 주입 라이브러리를 Hilt 에서 Metro로 Migration

🧪 테스트 내역 (선택)

  • 주요 기능 정상 동작 확인
  • 브라우저/기기에서 동작 확인
  • 엣지 케이스 테스트 완료
  • 기존 기능 영향 없음

📸 스크린샷 또는 시연 영상 (선택)

💬 추가 설명 or 리뷰 포인트 (선택)

Summary by CodeRabbit

  • 개선 사항
    • 앱 DI 인프라를 Metro 기반으로 전환하고 프레젠터/컴포넌트 팩토리와 스코프를 통일했습니다.
    • Compose 안정성 메타데이터가 정리되어 안정성 판정 정보가 더 명확해졌습니다.
  • 신규 기능
    • 런타임 구성 그래프(core:di)와 앱 컴포넌트 팩토리 추가로 구성 및 초기화 흐름이 분리되었습니다.
    • 일부 UI 컴포저블에 새로운 매개변수(예: 버튼의 trailingIcon)가 추가되었습니다.
  • 문서
    • 라이선스 목록에서 관련 항목명이 “Metro”로 업데이트되었습니다.

✏️ Tip: You can customize this high-level summary in your review settings.

Ui, Presenter metro 를 통한 의존성 주입 구성
CircuitModule, RepositoryModule 제거
각 :core 모듈별 그래프 생성하는 방식으로 변경
@BINDS 매핑 방식 수정, 코드 정리
AppGraph 내에 circuit 필드 변수 제거
plugin 하나만 주입하는거라 convention plugin 으로 만들 필요가 없음
CircuitGraph companion object -> interface
@coderabbitai
Copy link

coderabbitai bot commented Nov 21, 2025

Walkthrough

프로젝트 전반에서 Hilt를 Metro로 전환하는 리팩터링: 빌드 설정·플러그인·버전 카탈로그 수정, core:di 모듈 추가, Metro 기반 DI 그래프/팩토리 도입, 수많은 클래스/모듈의 DI 어노테이션·바인딩·팩토리 위치 및 스코프를 AppScope/DataScope로 전환.

Changes

코호트 / 파일(s) 변경 요약
빌드 설정 및 버전 카탈로그
\build.gradle.kts`, `gradle/libs.versions.toml`, build-logic/..., `settings.gradle.kts``
Hilt 플러그인/라이브러리 항목 제거, Metro 및 KSP 추가, 플러그인 alias 교체, :core:di 모듈 포함
앱 모듈 및 매니페스트
\app/build.gradle.kts`, `app/src/main/AndroidManifest.xml`, `app/src/main/kotlin/.../BooketApplication.kt``
Hilt 제거·KSP/metro 전환, android:appComponentFactory/tools:replace 추가, Application에 appGraph 초기화 추가
앱 DI 그래프 및 팩토리
\app/src/main/kotlin/.../di/AppGraph.kt`, `.../CircuitGraph.kt`, `.../MetroAppComponentFactory.kt``
AppGraph/CircuitGraph 인터페이스 추가(멀티바인딩 노출), MetroAppComponentFactory 구현 추가(액티비티/서비스 인스턴스 해석 및 제공자 맵 관리)
core/di 모듈(새로운 모듈)
\core/di/build.gradle.kts`, core/di/src/main/kotlin/com/ninecraft/booket/core/di/*``
core:di 모듈 생성, AppScope/DataScope, ActivityKey/ServiceKey/ApplicationContext 등 MapKey·Qualifier 정의 추가
DI 패턴 전환: 모듈→Graph
core/*/di/*, core/data/impl/**, core/datastore/impl/**, core/network/**, core/ocr/**
기존 Hilt @Module/@InstallIn 객체 제거, Metro @ContributesTo 기반 Graph 인터페이스 도입, 제공자 함수/바인딩을 Graph로 이동, 클래스에 @SingleIn(DataScope::class) 및 Metro @Inject 적용
DataStore 와 바인딩 재정의
core/datastore/impl/di/*
기존 DataStoreModule/DataStoreBindModule 삭제, DataStoreGraph로 DataStore 제공자 및 바인딩 재정의, DataStore API 노출(일부 의존성 변경)
Feature/Presenter/Ui DI 변경(대량)
feature/**/src/main/kotlin/**/*Presenter.kt, *Ui.kt (다수)
@AssistedInject를 클래스 레벨로 이동, 각 Presenter 내부로 Factory 추가(또는 위치 변경), @CircuitInject(..., ActivityRetainedComponent::class)@CircuitInject(..., AppScope::class) 전환, dagger.assisted.*dev.zacsweers.metro.* 교체
액티비티/서비스 생성자 주입 전환
feature/main/.../MainActivity.kt, app/.../ReedFirebaseMessagingService.kt
필드 주입 제거 → 생성자 주입으로 변경, 클래스에 Metro 기여 어노테이션(@ContributesIntoMap, @ActivityKey, @ServiceKey) 및 @Inject 추가
빌드로직/플러그인 컨벤션 변경
build-logic/src/main/kotlin/*, build-logic/build.gradle.kts
Android Hilt convention 제거, KSP/KSP extension 설정 및 metro 플러그인 사용으로 대체
문서 및 OSS 항목
README.md, feature/settings/src/main/assets/oss_licenses.json
README 및 OSS 라이선스에 Hilt 표기 → Metro로 변경
안정성(stability) 메타데이터 업데이트
**/*.stability, stability.config.conf, 일부 디자인시스템 파일
stability 파일에서 STABLE 주석 상세화(“class with no mutable properties”) 및 일부 내부 preview 삭제, ReedButtontrailingIcon 파라미터 추가 등 안정성 덤프 관련 변경

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Factory as MetroAppComponentFactory
    participant AppGraph as AppGraph (graph)
    participant Providers as provider maps
    participant Super as Android default

    App->>Factory: instantiateApplicationCompat(className)
    Factory->>Super: super.instantiateApplicationCompat(className)
    Super-->>Factory: Application instance
    Factory->>AppGraph: access BooketApplication.appGraph
    AppGraph-->>Factory: activityProviders & serviceProviders
    Factory->>Providers: set provider maps

    App->>Factory: instantiateActivityCompat(className, intent)
    alt provider found
        Factory->>Providers: lookup provider by class
        Providers-->>Factory: Provider
        Factory->>Provider: get instance
        Provider-->>Factory: Activity instance
    else not found
        Factory->>Super: super.instantiateActivityCompat(className, intent)
        Super-->>Factory: Activity instance
    end
    Factory-->>App: return Activity instance
Loading
sequenceDiagram
    participant Old as Hilt flow
    participant New as Metro flow

    Old->>Old: `@HiltAndroidApp` + `@AndroidEntryPoint` + 필드/컴포넌트 생성(Apt)
    Old-->>Old: Hilt가 컴포넌트/주입 코드를 생성

    New->>New: BooketApplication.appGraph 초기화 (createGraphFactory...create(this))
    New->>New: 생성자 주입 + `@ContributesIntoMap` 바인딩
    New-->>New: MetroAppComponentFactory가 provider 맵으로 Activity/Service 인스턴스 생성
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

검토 시 추가 주의 항목:

  • app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt: 클래스 로딩, 리플렉션/안전성(예: API 레벨 분기), provider 맵 초기화 시점
  • app/src/main/kotlin/.../AppGraph.kt, CircuitGraph.kt: 멀티바인딩 타입 정확성 및 factory 시그니처(Provider 제네릭 등)
  • core/data/impl/di/DataGraph.kt, core/datastore/impl/di/DataStoreGraph.kt: 바인딩이 올바른 구현에 매핑되었는지(속성 바인딩 패턴으로 인한 누락/잘못된 매핑)
  • core/network/**: TokenAuthenticator의 Provider 사용 방식 변경(serviceProvider() 호출) 검증
  • feature/*내 Presenter 변경 파일들: Factory 위치/어노테이션 변경으로 인한 코드 생성(코드제네레이션/KSP) 호환성 문제
  • AndroidManifest의 android:appComponentFactorytools:replace 적용 정확성

Poem

🐰 레일 위를 깡충깡충 Metro 토끼가 달려,
어노테이션 씨앗 옮겨 심고 스코프 잎을 정리해,
팩토리들이 모여 컴포넌트들을 빚어내니,
의존성 숲에 새 길이 생겼네, 토끼는 깡총! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목 'refactor: Hilt -> Metro Migration'은 주요 변경 사항을 명확하게 요약합니다. 간결하고 구체적이며 전체 마이그레이션의 핵심을 정확히 나타냅니다.
Linked Issues check ✅ Passed PR의 코드 변경사항이 연결된 이슈 #222의 목표를 모두 충족합니다. Hilt에서 Metro로의 완전한 마이그레이션이 구현되었으며, DI 관련 주요 요구사항을 모두 만족합니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 Hilt에서 Metro로의 DI 마이그레이션과 관련된 범위 내에 있습니다. 라이브러리 버전 업그레이드, 안정성 설정 개선, 그리고 관련된 보조 변경사항들이 마이그레이션 목표와 일치합니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch BOOK-434-refactor/#222

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (1)

45-49: RECORD_EDIT 상수 값이 RECORD_EDIT_SAVE와 동일하여 분석 이벤트 구분이 불가능합니다

현재 두 상수가 모두 "record_edit_save"로 설정되어 있어,

  • 화면 노출 이벤트(logScreenView(RECORD_EDIT))와
  • 저장 액션 이벤트(logEvent(RECORD_EDIT_SAVE))가
    동일한 이름으로 기록됩니다. 이름과 역할을 보면 RECORD_EDIT는 화면 뷰 이벤트용 "record_edit"이고, RECORD_EDIT_SAVE"record_edit_save" 여야 할 가능성이 높습니다.

아래처럼 상수 값을 분리하는 수정 제안드립니다.

     companion object {
         private const val MAX_PAGE = 4032
-        private const val RECORD_EDIT = "record_edit_save"
+        private const val RECORD_EDIT = "record_edit"
         private const val RECORD_EDIT_SAVE = "record_edit_save"
     }

이렇게 하면 화면 진입/저장 액션이 분석에서 명확히 구분될 것입니다.

🧹 Nitpick comments (11)
feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt (1)

35-42: @AssistedInjectnavigator 주입 패턴 적절 · 가시성만 좁히는 것 제안

클래스 레벨 @AssistedInject와 생성자 파라미터의 @Assisted navigator: Navigator 조합이 Metro + Circuit 통합 패턴과 자연스럽게 맞습니다. DI 동작 관점에서 특별한 문제는 없어 보입니다.

다만 navigator는 외부에서 직접 접근할 필요가 없는 내부 구현 세부사항이라, public 프로퍼티 대신 private으로 가시성을 줄이면 불필요한 API 노출을 조금 더 줄일 수 있습니다.

-@AssistedInject
-class SettingsPresenter(
-    @Assisted val navigator: Navigator,
+@AssistedInject
+class SettingsPresenter(
+    @Assisted private val navigator: Navigator,
core/network/build.gradle.kts (1)

6-10: core/network의 Metro 및 코루틴 의존성 설정

네트워크 모듈에 Metro 플러그인을 적용하고 projects.core.di + libs.kotlinx.coroutines.core를 추가한 것은 NetworkGraph를 Metro로 옮기려는 의도와 잘 맞고, 의존성 구성도 자연스럽습니다.

확인해 보면 좋을 부분은:

  • 이 모듈에서 DI 그래프(NetworkGraph 등)가 실제로 core.di를 참조하도록 구현되어 있는지 (불필요한 의존성이 아닌지)
  • 네트워크 코드 안에서 Dispatchers.Main을 직접 사용하는 경우가 있는지 여부
    • 있다면 coroutines-core만으로는 부족할 수 있어, 해당 용도는 kotlinx-coroutines-android 쪽에 두는 편이 더 명확할 수 있습니다.

구성 자체는 무난해 보이고, 현재 변경에는 이슈는 없어 보입니다.

Also applies to: 30-36

app/src/main/kotlin/com/ninecraft/booket/BooketApplication.kt (1)

8-13: Application DI 진입점(appGraph) 정의는 Metro 마이그레이션 의도와 잘 맞습니다.

createGraphFactory<AppGraph.Factory>().create(this)lazy로 올려둔 패턴은 스레드 세이프하고, MetroAppComponentFactory에서 Application을 통해 그래프에 접근하기에도 자연스러워 보입니다. 다만 외부 모듈에서 이 프로퍼티에 직접 의존하게 될 수 있으니, 필요하다면 val appGraph: AppGraph처럼 타입을 명시하고(문서화 효과) 가시성(internal 등)을 한 번 검토해 보셔도 좋겠습니다.

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt (1)

10-20: DefaultAuthRepository의 Metro DI 스코프/주입 설정은 일관적이지만 가시성(public) 확장은 한 번 점검해 주세요.

@SingleIn(DataScope::class) + 클래스 레벨 @Inject 조합은 Data 레이어 싱글턴 레포지토리로 쓰기에 자연스럽고, 기존 로직도 그대로 유지됩니다. 다만 이 구현체가 모듈 외부로 노출될 필요가 없다면, 이전처럼 internal을 유지해 모듈 경계를 더 강하게 가져가는 것도 고려해 볼 만합니다(외부에서는 AuthRepository 인터페이스만 보도록).

app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt (1)

1-52: setOnUnavailableContent 디버그 출력에서 릴리즈 빌드 정보 노출 검토 권장

  • Circuit의 newBuilder() 메서드와 presenterFactories/uiFactories 프로퍼티는 공개 API로 지원되므로, 코드에서 이 API들을 사용하는 것은 문제없습니다.

  • 다만 현재 구현의 setOnUnavailableContent는 모든 빌드(개발/릴리즈)에서 다음 정보를 그대로 노출합니다:

    • 등록되지 않은 route의 클래스 이름
    • 생성 가능한 Presenter/UI의 클래스 이름
    • 등록된 모든 presenterFactories/uiFactories 목록

    이는 개발 단계에서는 디버깅에 유용하지만, 릴리즈 빌드에서는 내부 구현 정보를 사용자에게 노출할 수 있습니다.

  • 선택 사항으로 BuildConfig.DEBUG 플래그로 분기하여, 릴리즈 빌드에서는 간단한 에러 메시지만 표시하도록 개선할 수 있습니다.

core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt (1)

17-23: 기존 Provider 기반 코드 주석은 정리해 두는 편이 좋겠습니다.

Metro로 마이그레이션하면서 Provider<ReedService> 기반 생성자 파라미터와 serviceProvider 호출이 주석으로만 남아 있고, 실제로는 Lazy<ReedService>를 사용하고 있습니다. 동작에는 문제 없지만, 주석이 남아 있으면 나중에 DI 경로를 파악할 때 혼란을 줄 수 있으니, 사용하지 않는 주석 코드는 삭제해 두는 편이 유지보수에 더 좋겠습니다.

Also applies to: 35-37

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseGraph.kt (1)

16-21: configSettingslazy는 제거해도 될 것 같습니다.

provideFirebaseRemoteConfig() 안에서

val configSettings by lazy {
    remoteConfigSettings { ... }
}
setConfigSettingsAsync(configSettings)

처럼 한 번만 사용하는 값에 by lazy를 쓰고 있어서, 일반 val 로 두고 바로 remoteConfigSettings { ... } 를 넘기는 편이 더 단순하고 읽기 쉽겠습니다. 동작에는 영향이 없으니, 나중에 정리하는 정도로만 고려해도 될 것 같습니다.

feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1)

30-34: Assisted 주입 + Circuit 통합 구조는 적절하며, 향후 @Inject + @Assisted 패턴으로 리팩터링할 것을 권장합니다.

  • @Assisted private val screen: RecordScreen, @Assisted private val navigator: Navigator@AssistedFactoryFactory.create(screen, navigator) 시그니처가 일치하여, Circuit에서 Screen 전체를 주입받는 기존 패턴과 잘 맞습니다.
  • @CircuitInject(RecordScreen::class, AppScope::class) 를 nested Factory에 붙인 것도 다른 Presenter들과의 일관성이 우수합니다. (learnings 기반)

Metro 0.7.x 문서에서는 assisted injection의 표준 패턴으로 @Inject 생성자 + @Assisted 파라미터 + @AssistedFactory 조합을 권장하고 있으므로, 향후 코드를 리팩터링할 때는 다음과 같이 통일하는 것을 권장합니다:

@Inject
class RecordRegisterPresenter(
    @Assisted private val screen: RecordScreen,
    @Assisted private val navigator: Navigator,
    ...
)

다만 @AssistedInject는 현재 문서상 deprecated 계획이 명시되지 않았으므로, 현재 코드는 그대로 유지하되 이후 Metro 버전 업그레이드 시점에 점진적으로 통일할 수 있습니다.

Also applies to: 37-49

core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt (1)

47-118: Metro 기반 NetworkGraph 전환은 무리 없이 잘 되어 있습니다

  • @ContributesTo(DataScope::class) + @Provides 조합으로 기존 Hilt 모듈을 자연스럽게 옮겨왔고, OkHttpClient/Retrofit/ReedService 구성도 기존과 동등하게 보입니다.
  • 성능 이슈는 아니지만, provideHttpLoggingInterceptor 안에서 message.lowercase()를 두 번 호출하고 있어서, 미세 최적화를 원하시면 한 번만 계산해서 로컬 변수로 재사용해도 됩니다.
feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt (1)

23-35: OnboardingPresenter의 Metro + Circuit DI 마이그레이션이 올바르게 보입니다

  • @AssistedInject를 클래스에 붙이고, navigator@Assisted 인자로 분리한 뒤, 내부 Factory@CircuitInject(OnboardingScreen::class, AppScope::class) / @AssistedFactory를 붙인 구조가 Metro + Circuit 패턴과 잘 맞습니다.
  • 유지보수성 측면에서, 마지막 페이지 판별 시 event.currentPage == 2 대신 event.currentPage == ONBOARDING_STEPS_COUNT - 1 를 사용하면 추후 온보딩 단계 수가 바뀌어도 실수할 여지가 줄어들 것 같습니다.
app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt (1)

13-60: MetroAppComponentFactory 패턴은 적절해 보이지만 Application 캐스팅은 조금 더 방어적으로 가져가면 좋겠습니다

  • instantiateActivityCompat / instantiateServiceCompat 에서 Metro provider를 먼저 시도하고, 없으면 super로 위임하는 흐름은 깔끔합니다.
  • 다만 instantiateApplicationCompat 에서 app as BooketApplication 로 단정 캐스팅하고 있어, 추후 테스트용 Application 을 따로 쓰거나 다른 프로세스에서 별도 Application 을 쓰게 되면 바로 ClassCastException 으로 죽을 수 있습니다.
  • 아래처럼 안전 캐스팅과 가드로 감싸 두면 재사용성이 좋아지고, Metro를 쓰지 않는 특수 케이스에서도 AppComponentFactory 를 그대로 재활용하기 쉬워집니다.
override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
    val app = super.instantiateApplicationCompat(cl, className)
    if (app is BooketApplication) {
        val appGraph = app.appGraph
        activityProviders = appGraph.activityProviders
        serviceProviders = appGraph.serviceProviders
    }
    return app
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b5e477 and f538d44.

📒 Files selected for processing (104)
  • README.md (1 hunks)
  • app/build.gradle.kts (3 hunks)
  • app/src/main/AndroidManifest.xml (2 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/BooketApplication.kt (1 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (2 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/di/AppGraph.kt (1 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt (1 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitModule.kt (0 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt (1 hunks)
  • build-logic/build.gradle.kts (1 hunks)
  • build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt (3 hunks)
  • build-logic/src/main/kotlin/AndroidHiltConventionPlugin.kt (0 hunks)
  • build-logic/src/main/kotlin/com/ninecraft/booket/convention/Plugins.kt (1 hunks)
  • build.gradle.kts (1 hunks)
  • core/common/build.gradle.kts (1 hunks)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/AnalyticsHelper.kt (1 hunks)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsGraph.kt (1 hunks)
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt (0 hunks)
  • core/data/impl/build.gradle.kts (2 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/DataGraph.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/di/FirebaseGraph.kt (2 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultBookRepository.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRecordRepository.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultRemoteConfigRepository.kt (1 hunks)
  • core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultUserRepository.kt (2 hunks)
  • core/datastore/impl/build.gradle.kts (2 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLibraryRecentSearchDataSource.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultNotificationDataSource.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultOnboardingDataSource.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultTokenDataSource.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt (0 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt (1 hunks)
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/security/CryptoManager.kt (1 hunks)
  • core/di/build.gradle.kts (1 hunks)
  • core/di/src/main/kotlin/com/ninecraft/booket/core/di/ActivityKey.kt (1 hunks)
  • core/di/src/main/kotlin/com/ninecraft/booket/core/di/ApplicationContext.kt (1 hunks)
  • core/di/src/main/kotlin/com/ninecraft/booket/core/di/DataScope.kt (1 hunks)
  • core/di/src/main/kotlin/com/ninecraft/booket/core/di/ServiceKey.kt (1 hunks)
  • core/network/build.gradle.kts (2 hunks)
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt (2 hunks)
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenInterceptor.kt (1 hunks)
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt (5 hunks)
  • core/ocr/build.gradle.kts (2 hunks)
  • core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/CloudVisionNetworkQualifier.kt (1 hunks)
  • core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/di/OcrGraph.kt (2 hunks)
  • core/ocr/src/main/kotlin/com/ninecraft/booket/core/ocr/recognizer/CloudOcrRecognizer.kt (1 hunks)
  • feature/detail/build.gradle.kts (0 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt (2 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt (1 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt (1 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt (1 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt (1 hunks)
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt (1 hunks)
  • feature/edit/build.gradle.kts (0 hunks)
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt (1 hunks)
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditUi.kt (1 hunks)
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt (1 hunks)
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt (1 hunks)
  • feature/home/build.gradle.kts (0 hunks)
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt (1 hunks)
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt (1 hunks)
  • feature/library/build.gradle.kts (0 hunks)
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt (1 hunks)
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt (1 hunks)
  • feature/login/build.gradle.kts (0 hunks)
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/KakaoLoginClient.kt (1 hunks)
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt (1 hunks)
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt (1 hunks)
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt (1 hunks)
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt (1 hunks)
  • feature/main/build.gradle.kts (1 hunks)
  • feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (2 hunks)
  • feature/onboarding/build.gradle.kts (0 hunks)
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt (1 hunks)
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt (1 hunks)
  • feature/record/build.gradle.kts (0 hunks)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt (1 hunks)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt (1 hunks)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt (1 hunks)
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt (1 hunks)
  • feature/search/build.gradle.kts (0 hunks)
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt (1 hunks)
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt (1 hunks)
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt (1 hunks)
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt (1 hunks)
  • feature/settings/build.gradle.kts (0 hunks)
  • feature/settings/src/main/assets/oss_licenses.json (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationUi.kt (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt (1 hunks)
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesUi.kt (1 hunks)
  • feature/splash/build.gradle.kts (0 hunks)
  • feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt (1 hunks)
  • feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashUi.kt (1 hunks)
  • feature/webview/build.gradle.kts (0 hunks)
  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt (1 hunks)
  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt (1 hunks)
  • gradle/libs.versions.toml (5 hunks)
  • settings.gradle.kts (1 hunks)
💤 Files with no reviewable changes (15)
  • feature/webview/build.gradle.kts
  • feature/onboarding/build.gradle.kts
  • feature/settings/build.gradle.kts
  • build-logic/src/main/kotlin/AndroidHiltConventionPlugin.kt
  • core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt
  • feature/record/build.gradle.kts
  • feature/login/build.gradle.kts
  • feature/splash/build.gradle.kts
  • feature/edit/build.gradle.kts
  • feature/detail/build.gradle.kts
  • feature/library/build.gradle.kts
  • feature/home/build.gradle.kts
  • feature/search/build.gradle.kts
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitModule.kt
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreModule.kt
🧰 Additional context used
🧠 Learnings (10)
📚 Learning: 2025-07-20T12:34:23.786Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 61
File: feature/webview/build.gradle.kts:17-21
Timestamp: 2025-07-20T12:34:23.786Z
Learning: Reed-Android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 plugin은 Circuit, Compose, 그리고 core 모듈들의 의존성을 자동으로 포함하므로, 각 feature 모듈의 build.gradle.kts에서는 특별한 의존성(예: libs.logger, libs.kakao.auth)만 별도로 선언하면 된다.

Applied to files:

  • core/common/build.gradle.kts
  • core/network/build.gradle.kts
  • core/di/build.gradle.kts
  • build-logic/build.gradle.kts
  • feature/main/build.gradle.kts
  • README.md
  • feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
  • build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt
  • core/datastore/impl/build.gradle.kts
  • core/ocr/build.gradle.kts
  • app/build.gradle.kts
  • core/data/impl/build.gradle.kts
  • gradle/libs.versions.toml
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt
📚 Learning: 2025-07-31T23:30:37.547Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.547Z
Learning: In Circuit architecture, presenters receive the Screen object directly as a constructor parameter (e.g., Assisted private val screen: RecordDetailScreen), and screen parameters are accessed through this screen object (e.g., screen.recordId). Screen parameters should not be added as separate constructor parameters.

Applied to files:

  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardUi.kt
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditUi.kt
  • feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt
📚 Learning: 2025-07-31T23:30:37.547Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 88
File: feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt:18-47
Timestamp: 2025-07-31T23:30:37.547Z
Learning: In Circuit architecture, screen parameters are not manually passed to presenter constructors. The CircuitInject annotation automatically handles the injection of the entire Screen object, making screen parameters accessible without explicitly adding them to the constructor or factory methods.

Applied to files:

  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/emotion/EmotionEditPresenter.kt
  • feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomeUi.kt
  • feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt
📚 Learning: 2025-07-29T07:02:18.885Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 77
File: feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt:73-73
Timestamp: 2025-07-29T07:02:18.885Z
Learning: Kotlin에서 같은 패키지 내의 파일들은 패키지 레벨에 정의된 const val 상수를 import 없이 직접 접근할 수 있습니다. OnboardingPresenter.kt와 OnboardingUi.kt 모두 com.ninecraft.booket.feature.onboarding 패키지에 속해 있어서 ONBOARDING_STEPS_COUNT 상수를 공유할 수 있습니다.

Applied to files:

  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt
📚 Learning: 2025-08-28T12:25:54.058Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 174
File: feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt:128-133
Timestamp: 2025-08-28T12:25:54.058Z
Learning: In BookSearchPresenter.kt, when a guest user tries to register a book and is redirected to login, the bottom sheet (isBookRegisterBottomSheetVisible) and selection state (selectedBookIsbn, selectedBookStatus) are intentionally kept open/preserved so that when the user returns from login, they can continue from where they left off without losing context.

Applied to files:

  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingUi.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsUi.kt
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryUi.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterUi.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchUi.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailUi.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt
  • feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultBookRecentSearchDataSource.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt
📚 Learning: 2025-07-16T15:54:19.322Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 52
File: feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/screens/DelegatingNavigator.kt:0-0
Timestamp: 2025-07-16T15:54:19.322Z
Learning: In the Reed-Android project using Circuit architecture, DelegatingNavigator was restored because NavigableCircuitContent can only inject a single navigator, but the dual-navigator architecture (childNavigator for bottom navigation screens, rootNavigator for full-screen screens) requires a delegating component to work within Circuit's constraint.

Applied to files:

  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewUi.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/card/RecordCardPresenter.kt
  • feature/splash/src/main/kotlin/com/ninecraft/booket/splash/SplashPresenter.kt
  • feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt
  • app/src/main/kotlin/com/ninecraft/booket/di/CircuitGraph.kt
  • feature/library/src/main/kotlin/com/ninecraft/booket/feature/library/LibraryPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
  • feature/webview/src/main/kotlin/com/ninecraft/booket/feature/webview/WebViewPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/library/LibrarySearchPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/SettingsPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/osslicenses/OssLicensesPresenter.kt
  • feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/register/RecordRegisterPresenter.kt
  • feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
  • feature/settings/src/main/kotlin/com/ninecraft/booket/feature/settings/notification/NotificationPresenter.kt
  • feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementPresenter.kt
  • feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrPresenter.kt
  • feature/onboarding/src/main/kotlin/com/ninecraft/booket/feature/onboarding/OnboardingPresenter.kt
  • feature/search/src/main/kotlin/com/ninecraft/booket/feature/search/book/BookSearchPresenter.kt
📚 Learning: 2025-07-09T01:14:29.836Z
Learnt from: seoyoon513
Repo: YAPP-Github/Reed-Android PR: 35
File: feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/TermsAgreementScreen.kt:127-127
Timestamp: 2025-07-09T01:14:29.836Z
Learning: In the Reed-Android project's TermsAgreementScreen.kt, the OnTermDetailClick event is intentionally passed an empty string for the URL parameter because the actual URLs for terms detail pages haven't been decided yet. This is a temporary implementation that will be updated once the URLs are finalized.

Applied to files:

  • feature/login/src/main/kotlin/com/ninecraft/booket/feature/termsagreement/TermsAgreementUi.kt
📚 Learning: 2025-07-14T00:51:38.952Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 46
File: core/network/src/main/kotlin/com/ninecraft/booket/core/network/response/BookSearchResponse.kt:33-44
Timestamp: 2025-07-14T00:51:38.952Z
Learning: The project uses Kotlinx Serialization with JSON configuration that includes `ignoreUnknownKeys = true`, `encodeDefaults = true`, `isLenient = true`, and `prettyPrint = true` to handle API response parsing robustly, making additional null safety measures less critical if the API consistently provides all expected fields.

Applied to files:

  • gradle/libs.versions.toml
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt
📚 Learning: 2025-08-08T17:16:02.854Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 0
File: :0-0
Timestamp: 2025-08-08T17:16:02.854Z
Learning: Reed-Android 프로젝트에서 Retrofit + kotlinx.serialization 조합 사용 시, 서버가 JSON 응답을 반환하는 API에서 Repository가 `Result<Unit>`을 반환하면 런타임에 타입 소거로 인해 Unit이 Any(java.lang.Object)로 취급되어 "Serializer for class 'Any' is not found" 에러가 발생한다. 이를 해결하려면 구체적인 데이터 클래스를 반환 타입으로 사용해야 한다.

Applied to files:

  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt
📚 Learning: 2025-08-08T17:23:23.833Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 122
File: core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/mapper/ResponseToModel.kt:242-250
Timestamp: 2025-08-08T17:23:23.833Z
Learning: Reed-Android 프로젝트에서 Retrofit과 kotlinx.serialization을 함께 사용할 때, API 메서드가 Unit을 반환하면 "Serializer for class 'Any' is not found" 에러가 발생한다. 이는 kotlinx.serialization이 Unit 타입에 대한 serializer를 제공하지 않기 때문이며, 실제 응답 모델을 반환하도록 변경하여 해결한다.

Applied to files:

  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/di/NetworkGraph.kt
🧬 Code graph analysis (4)
build-logic/build.gradle.kts (1)
build-logic/src/main/kotlin/com/ninecraft/booket/convention/Dependencies.kt (1)
  • compileOnly (38-40)
app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (1)
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (1)
  • binding (34-102)
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (1)
app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (1)
  • binding (27-98)
core/datastore/impl/build.gradle.kts (1)
build-logic/src/main/kotlin/com/ninecraft/booket/convention/Dependencies.kt (1)
  • api (10-12)
⏰ 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: Compose Stability Check
  • GitHub Check: ci-build

compileOnly(libs.android.gradle.plugin)
compileOnly(libs.kotlin.gradle.plugin)
compileOnly(libs.compose.compiler.gradle.plugin)
compileOnly(libs.ksp.gradle.plugin)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Metro 및 KSP 버전 호환성을 확인하세요.

KSP 플러그인 의존성이 추가되었습니다. Metro DI 프레임워크로의 마이그레이션에 필요한 변경사항이지만, 사용 중인 Metro 및 KSP 버전이 서로 호환되는지 확인해야 합니다.

다음 스크립트를 실행하여 버전 정보를 확인하세요:

또한 최신 Metro 문서를 확인하여 권장 KSP 버전을 확인하세요:


🏁 Script executed:

#!/bin/bash
# Description: Metro와 KSP 버전 확인

# Metro 버전 확인
echo "=== Metro 버전 ==="
rg -n "metro.*=.*\"" gradle/libs.versions.toml

# KSP 버전 확인  
echo "=== KSP 버전 ==="
rg -n "ksp.*=.*\"" gradle/libs.versions.toml

# Metro plugin 사용 확인
echo "=== Metro plugin 사용 ==="
rg -n "alias\(libs\.plugins\.metro\)" --type kotlin

Length of output: 949


🌐 Web query:

What is the recommended KSP version for Metro DI library?

💡 Result:

Use KSP 2.2.20-2.0.3. [1][2]

Sources:
[1] Metro installation example showing ksp-version = "2.2.20-2.0.3". [2] Metro compatibility notes (Kotlin 2.2.0 / Metro 0.4.0+).


KSP 버전이 Metro와 호환되지 않습니다.

프로젝트는 KSP 2.3.0을 사용 중이지만, Metro 0.7.7은 KSP 2.2.20-2.0.3을 권장합니다. KSP 2.3.0은 권장 버전보다 최신이므로 Metro와의 호환성 문제가 발생할 수 있습니다. gradle/libs.versions.toml의 KSP 버전을 권장 버전으로 변경하세요.

ksp = "2.2.20-2.0.3"
🤖 Prompt for AI Agents
In build-logic/build.gradle.kts around line 12, the project pins KSP to a newer,
incompatible version via compileOnly(libs.ksp.gradle.plugin); update the KSP
entry in gradle/libs.versions.toml to the Metro-recommended value ksp =
"2.2.20-2.0.3" so libs.ksp.gradle.plugin resolves to that compatible version,
then refresh Gradle (or run ./gradlew --refresh-dependencies and a build) to
verify the plugin now uses the updated KSP version.

Comment on lines +25 to +43
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject
import kotlinx.coroutines.launch

class RecordEditPresenter @AssistedInject constructor(
@AssistedInject
class RecordEditPresenter(
@Assisted private val screen: RecordEditScreen,
@Assisted private val navigator: Navigator,
private val repository: RecordRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<RecordEditUiState> {

@CircuitInject(RecordEditScreen::class, AppScope::class)
@AssistedFactory
fun interface Factory {
fun create(screen: RecordEditScreen, navigator: Navigator): RecordEditPresenter
}
Copy link

@coderabbitai coderabbitai bot Nov 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Metro 기반 Assisted DI 마이그레이션 전반적으로 일관성 있음

  • @AssistedInject + @Assisted + @AssistedFactory 조합과 CircuitInject(RecordEditScreen::class, AppScope::class) 패턴이 다른 화면 Presenter들과 유사한 형태로 보이고, RecordEditScreen/Navigator를 Assisted 파라미터로만 받는 것도 Circuit 아키텍처 가이드와 잘 맞습니다. Screen 필드를 그대로 주입받고 내부에서 screen.recordInfo 등을 사용하는 구조 유지한 점 좋습니다. (Based on learnings)
  • 다만 Metro 쪽에서 dev.zacsweers.metro.AssistedInject / AssistedFactory를 어떻게 매핑하는지, 그리고 현재 사용하는 Metro 버전에서 이 조합이 정식 권장 패턴인지 한 번만 다시 확인해 주세요. 최신 Metro 문서는 @Inject + @Assisted + @AssistedFactory 패턴을 기본 예제로 설명하고 있어서, 장기적으로는 해당 형태로 통일하는 것도 고려해 볼 수 있습니다.(zacsweers.github.io)

🌐 Web query:

Metro에서 `dev.zacsweers.metro.AssistedInject` / `AssistedFactory` 조합이 `@Inject + @Assisted + @AssistedFactory`와 동등하게 동작하는지, 그리고 현재 사용하는 Metro 버전에서 권장되는 Assisted injection 패턴이 무엇인지 확인해 주세요.

💡 Result:

요약: Metro에서는 Dagger 스타일의 별도 @AssistedInject 어노테이션이 아니라 표준 @Inject(생성자) + @Assisted(파라미터) + @AssistedFactory(팩토리)를 사용합니다. 따라서 질문에 적힌 조합(“dev.zacsweers.metro.AssistedInject / AssistedFactory”)이라는 표기에서 핵심은 Metro의 Assisted 흐름이 Dagger의 Assisted 패턴과 동등하게 동작한다는 점입니다 — Metro는 주입 가능한 생성자는 @Inject로 표시하고, 동적(assisted) 파라미터는 @Assisted로 표시하며, 해당 생성자를 생성하는 팩토리는 @AssistedFactory로 선언해서 사용합니다. 자동 생성된 assisted-factory 기능도 옵션으로 제공됩니다(컴파일러 플래그/옵션으로 활성화 가능). [1][2][3]

권장 패턴 (현재 Metro 문서 기준)

  • 기본: 클래스 생성자에 @Inject, 동적 파라미터에 @Assisted, 팩토리 인터페이스에 @AssistedFactory를 사용. 팩토리의 추상 메서드가 assisted 파라미터를 받아 대상 타입을 반환하도록 작성. 이것이 문서상 권장되는 표준 방식입니다.
  • 보일러플레이트를 줄이고 싶다면 Metro의 “automatic assisted factory generation” 기능을 활성화해 자동으로 팩토리를 생성하도록 할 수 있음(단, 일부 사용 사례에서는 수동 팩토리가 필요할 수 있음).
  • kotlin-inject 등 기존 어노테이션과 혼용하려면 Metro의 interop 설정을 사용해 점진 마이그레이션 가능.

참고 문서

  • Metro 디자인/Assisted Injection 설명. [1]
  • Metro 소개·예제 (@Inject + @assisted + @AssistedFactory 사용 예). [2]
  • Metro adoption/interop 가이드(기존 라이브러리 마이그레이션 관련). [3]

원하시면 현재 사용 중인 Metro 버전(프로젝트의 Gradle 설정)을 알려주시면 그 버전에서 자동 팩토리 옵션 사용법(컴파일러 플래그/Gradle 설정)과 구체적인 예제 코드를 바로 보여드리겠습니다.


Metro의 공식 권장 패턴으로 마이그레이션 필요

현재 코드는 @AssistedInject를 사용하고 있으나, Metro 공식 문서의 권장 패턴은 @Inject(생성자) + @Assisted(파라미터) + @AssistedFactory(팩토리)입니다. 현재 조합이 기술적으로 동작할 수 있지만, 공식 문서상 표준이 아니므로 다음과 같이 수정을 권장합니다:

수정 제안:

@Inject  // 변경: @AssistedInject → @Inject
class RecordEditPresenter(
    @Assisted private val screen: RecordEditScreen,
    @Assisted private val navigator: Navigator,
    private val repository: RecordRepository,
    private val analyticsHelper: AnalyticsHelper,
) : Presenter<RecordEditUiState> {

    @CircuitInject(RecordEditScreen::class, AppScope::class)
    @AssistedFactory
    fun interface Factory {
        fun create(screen: RecordEditScreen, navigator: Navigator): RecordEditPresenter
    }

이렇게 하면 Metro의 표준 Assisted injection 패턴과 일치하며, 장기적 유지보수성이 개선됩니다. @CircuitInject는 그대로 유지하되, 클래스 수준 어노테이션만 @Inject로 통일하시기 바랍니다.

🤖 Prompt for AI Agents
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
around lines 25-43: replace the non-standard Metro usage by changing the
class-level annotation from @AssistedInject to @Inject, keep @Assisted on the
constructor parameters and retain the @AssistedFactory and @CircuitInject as-is;
also update imports accordingly (remove dev.zacsweers.metro.AssistedInject
import and add the appropriate javax.inject.Inject import) so the presenter
follows Metro's recommended Assisted injection pattern.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

https://zacsweers.github.io/metro/latest/changelog/#067
이 change log 보면 알겠지만 Assisted-injected classes must now use @AssistedInject instead of @Inject. 라고 언급됨
이 변경사항은 0.6.7버전 이후부터 적용이고 현재 나는 0.7.6 버전 쓰고 있음

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines +29 to 36
@AssistedInject
class LoginPresenter(
@Assisted private val screen: LoginScreen,
@Assisted private val navigator: Navigator,
private val authRepository: AuthRepository,
private val userRepository: UserRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<LoginUiState> {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

@AssistedInject 적용 대상이 잘못되었습니다.
Metro의 @AssistedInject는 기본 생성자에 붙어야 KSP가 팩토리를 생성합니다. 현재처럼 클래스 선언에만 붙이면 주입 코드가 생성되지 않아 LoginPresenter 팩토리가 빌드타임에 실패하거나 런타임 주입이 깨집니다. 아래처럼 constructor에 직접 적용해 주세요.

-@AssistedInject
-class LoginPresenter(
+class LoginPresenter @AssistedInject constructor(
     @Assisted private val screen: LoginScreen,
     @Assisted private val navigator: Navigator,
     ...
🤖 Prompt for AI Agents
In
feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt
around lines 29 to 36, @AssistedInject is applied to the class declaration but
Metro's KSP expects it on the primary constructor; move the annotation from the
class header to the constructor (apply @AssistedInject directly before the
constructor keyword), leaving @Assisted on the two assisted params and keeping
the other injected properties as-is so the factory is generated correctly.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (4)
core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt (2)

19-23: 주석 처리된 코드 제거 권장

Line 22의 주석 처리된 serviceLazy 파라미터는 사용되지 않는 코드입니다. 코드 정리 차원에서 제거를 고려해주세요.

다음 diff를 적용하여 주석을 제거할 수 있습니다:

 class TokenAuthenticator(
     private val tokenDataSource: TokenDataSource,
     private val serviceProvider: Provider<ReedService>,
-    // private val serviceLazy: Lazy<ReedService>,
 ) : Authenticator {

36-37: 중복된 주석 제거 권장

Line 37은 Line 36과 동일한 코드가 주석 처리되어 있습니다. 불필요한 중복이므로 제거를 권장합니다.

다음 diff를 적용하여 중복 주석을 제거할 수 있습니다:

                 val refreshTokenRequest = RefreshTokenRequest(refreshToken)
                 val refreshResponse = serviceProvider().refreshToken(refreshTokenRequest)
-                // val refreshResponse = serviceProvider().refreshToken(refreshTokenRequest)
 
                 tokenDataSource.apply {
app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt (2)

21-29: reflection 실패 시 에러 처리 개선을 고려하세요.

현재 Class.forName이 실패하면 exception이 전파됩니다. 이는 의도된 동작일 수 있지만, ClassNotFoundException을 catch하여 로깅하는 것이 디버깅에 도움이 될 수 있습니다.


57-60: provider map 필드의 불변성을 고려하세요.

lateinit var는 재할당이 가능하여 의도하지 않은 변경이 발생할 수 있습니다. 한 번만 초기화되어야 한다면 더 방어적인 패턴을 사용하는 것을 고려하세요.

다음과 같이 리팩터링할 수 있습니다:

     companion object {
-        private lateinit var activityProviders: Map<KClass<out Activity>, Provider<Activity>>
-        private lateinit var serviceProviders: Map<KClass<out Service>, Provider<Service>>
+        @Volatile
+        private var activityProviders: Map<KClass<out Activity>, Provider<Activity>>? = null
+        @Volatile
+        private var serviceProviders: Map<KClass<out Service>, Provider<Service>>? = null
     }

그리고 접근 시:

activityProviders?.let { providers ->
    getInstance(cl, className, providers)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f538d44 and e998355.

📒 Files selected for processing (16)
  • app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (2 hunks)
  • app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt (1 hunks)
  • core/di/build.gradle.kts (1 hunks)
  • core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt (2 hunks)
  • feature/detail/build.gradle.kts (0 hunks)
  • feature/edit/build.gradle.kts (0 hunks)
  • feature/home/build.gradle.kts (0 hunks)
  • feature/library/build.gradle.kts (0 hunks)
  • feature/login/build.gradle.kts (0 hunks)
  • feature/main/build.gradle.kts (1 hunks)
  • feature/onboarding/build.gradle.kts (0 hunks)
  • feature/record/build.gradle.kts (0 hunks)
  • feature/search/build.gradle.kts (0 hunks)
  • feature/settings/build.gradle.kts (0 hunks)
  • feature/splash/build.gradle.kts (0 hunks)
  • feature/webview/build.gradle.kts (0 hunks)
💤 Files with no reviewable changes (11)
  • feature/edit/build.gradle.kts
  • feature/settings/build.gradle.kts
  • feature/home/build.gradle.kts
  • feature/library/build.gradle.kts
  • feature/splash/build.gradle.kts
  • feature/webview/build.gradle.kts
  • feature/onboarding/build.gradle.kts
  • feature/login/build.gradle.kts
  • feature/detail/build.gradle.kts
  • feature/search/build.gradle.kts
  • feature/record/build.gradle.kts
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/di/build.gradle.kts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-20T12:34:23.786Z
Learnt from: easyhooon
Repo: YAPP-Github/Reed-Android PR: 61
File: feature/webview/build.gradle.kts:17-21
Timestamp: 2025-07-20T12:34:23.786Z
Learning: Reed-Android 프로젝트에서는 `booket.android.feature` convention plugin을 사용하여 feature 모듈들의 공통 의존성을 관리한다. 이 plugin은 Circuit, Compose, 그리고 core 모듈들의 의존성을 자동으로 포함하므로, 각 feature 모듈의 build.gradle.kts에서는 특별한 의존성(예: libs.logger, libs.kakao.auth)만 별도로 선언하면 된다.

Applied to files:

  • feature/main/build.gradle.kts
🧬 Code graph analysis (1)
app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (1)
feature/main/src/main/kotlin/com/ninecraft/booket/feature/main/MainActivity.kt (1)
  • binding (34-102)
⏰ 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: Compose Stability Check
  • GitHub Check: ci-build
🔇 Additional comments (5)
feature/main/build.gradle.kts (1)

13-14: Metro 마이그레이션 관련 core.di 의존성 추가가 올바르게 구현되었습니다.

검증 결과:

  • core/di 모듈이 Metro 플러그인으로 적절히 설정됨
  • ✓ feature/main은 앱 진입점(MainActivity.kt 포함)이므로 명시적 core.di 의존성 필요
  • ✓ 다른 feature 모듈들은 convention plugin으로 공유 의존성 관리 (일관된 패턴)
  • ✓ 의존성 선언 문법 정확함

코드 변경이 Metro DI 인프라 중앙화 목표와 일치하며, 모듈 아키텍처와 완벽하게 정렬됩니다.

core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt (1)

17-18: 적절한 스코프 지정

TokenAuthenticator@SingleIn(DataScope::class)를 적용한 것은 네트워크 레이어의 인증 처리기로서 적절한 싱글톤 스코프입니다.

app/src/main/kotlin/com/ninecraft/booket/ReedFirebaseMessagingService.kt (2)

6-6: 임포트 변경 사항이 적절합니다.

Metro DI 프레임워크로의 마이그레이션에 필요한 임포트가 올바르게 추가되었습니다. Service 타입은 binding<Service>()에 필요하며, Metro 관련 어노테이션과 스코프가 모두 정확히 임포트되었습니다.

Also applies to: 15-15, 17-20


27-32: Metro Service 주입 인프라가 올바르게 설정되어 있음을 확인했습니다. 추가 검증을 진행하겠습니다.

Metro Service 주입이 올바르게 구성되어 있습니다. 변경 사항을 승인합니다.

검증 결과, Metro의 Service 주입 인프라가 완벽하게 설정되어 있습니다:

  • @ContributesIntoMap과 @ServiceKey는 함께 작동하여 그래프에 클래스 타입의 동일한 클래스들을 정의하되, 팩토리가 어떤 특정 클래스를 제공할 것인지를 알도록 하는 Key-Value 맵을 빌드합니다.
  • 매니페스트에 MetroAppComponentFactory가 등록되었고, instantiateServiceCompat()이 serviceProviders 맵에서 ReedFirebaseMessagingService를 올바르게 조회합니다.
  • ReedFirebaseMessagingService는 Android Service를 확장하므로 serviceProviders 맵 서명과 완벽하게 일치합니다.

생성자 주입으로의 전환은 모범 사례이며, Metro의 멀티바인딩 패턴이 올바르게 적용되었습니다. 의존성 주입이 런타임에 정상 작동할 것입니다.

app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt (1)

13-19: LGTM!

클래스 선언과 문서화가 명확합니다. @keep 어노테이션은 reflection 기반 컴포넌트 팩토리에 적절합니다.

Comment on lines +31 to +38
override fun instantiateActivityCompat(
cl: ClassLoader,
className: String,
intent: Intent?,
): Activity {
return getInstance(cl, className, activityProviders)
?: super.instantiateActivityCompat(cl, className, intent)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

lateinit 변수 접근 시 초기화 순서 문제가 발생할 수 있습니다.

activityProvidersinstantiateApplicationCompat에서 초기화되지만, Activity가 Application보다 먼저 생성되는 경우 UninitializedPropertyAccessException이 발생합니다. 초기화 여부를 확인하거나 nullable 타입으로 변경하는 것을 고려하세요.

다음 diff를 적용하여 초기화 체크를 추가할 수 있습니다:

     override fun instantiateActivityCompat(
         cl: ClassLoader,
         className: String,
         intent: Intent?,
     ): Activity {
-        return getInstance(cl, className, activityProviders)
+        return if (::activityProviders.isInitialized) {
+            getInstance(cl, className, activityProviders)
+        } else {
+            null
+        }
             ?: super.instantiateActivityCompat(cl, className, intent)
     }
📝 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.

Suggested change
override fun instantiateActivityCompat(
cl: ClassLoader,
className: String,
intent: Intent?,
): Activity {
return getInstance(cl, className, activityProviders)
?: super.instantiateActivityCompat(cl, className, intent)
}
override fun instantiateActivityCompat(
cl: ClassLoader,
className: String,
intent: Intent?,
): Activity {
return if (::activityProviders.isInitialized) {
getInstance(cl, className, activityProviders)
} else {
null
}
?: super.instantiateActivityCompat(cl, className, intent)
}
🤖 Prompt for AI Agents
In app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt
around lines 31 to 38, accessing the lateinit activityProviders in
instantiateActivityCompat can throw UninitializedPropertyAccessException if an
Activity is created before instantiateApplicationCompat initializes it; change
the code to guard access by either checking initialization
(::activityProviders.isInitialized) before using it or make activityProviders
nullable and use a safe-call (activityProviders?.let { ... } ?:
super.instantiateActivityCompat(...)) so you only use activityProviders when
initialized and otherwise fall back to the super implementation.

Comment on lines +40 to +47
override fun instantiateServiceCompat(
cl: ClassLoader,
className: String,
intent: Intent?,
): Service {
return getInstance(cl, className, serviceProviders)
?: super.instantiateServiceCompat(cl, className, intent)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

lateinit 변수 접근 시 초기화 순서 문제가 발생할 수 있습니다.

serviceProviders도 동일한 초기화 순서 문제를 가지고 있습니다. Activity와 동일한 방식으로 초기화 체크를 추가해야 합니다.

다음 diff를 적용하세요:

     override fun instantiateServiceCompat(
         cl: ClassLoader,
         className: String,
         intent: Intent?,
     ): Service {
-        return getInstance(cl, className, serviceProviders)
+        return if (::serviceProviders.isInitialized) {
+            getInstance(cl, className, serviceProviders)
+        } else {
+            null
+        }
             ?: super.instantiateServiceCompat(cl, className, intent)
     }
🤖 Prompt for AI Agents
In app/src/main/kotlin/com/ninecraft/booket/di/MetroAppComponentFactory.kt
around lines 40 to 47, accessing the lateinit serviceProviders can lead to
initialization-order crashes; add the same initialization check used for
Activity providers before using it. Modify instantiateServiceCompat to verify
::serviceProviders.isInitialized (or a safe null/initialized guard) and only
call getInstance when initialized, otherwise fall back to
super.instantiateServiceCompat; ensure no direct access to the lateinit property
without the guard to avoid UninitializedPropertyAccessException.

Comment on lines +49 to +55
override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
val app = super.instantiateApplicationCompat(cl, className)
val appGraph = (app as BooketApplication).appGraph
activityProviders = appGraph.activityProviders
serviceProviders = appGraph.serviceProviders
return app
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

타입 캐스팅과 thread safety 문제가 있습니다.

  1. Line 51의 unsafe cast: BooketApplication으로의 캐스팅이 실패하면 ClassCastException이 발생합니다.
  2. Thread safety: companion object의 lateinit var에 대한 동시 할당이 동기화되지 않아 race condition이 발생할 수 있습니다.

다음 diff를 적용하여 안전성을 개선하세요:

     override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
         val app = super.instantiateApplicationCompat(cl, className)
-        val appGraph = (app as BooketApplication).appGraph
-        activityProviders = appGraph.activityProviders
-        serviceProviders = appGraph.serviceProviders
+        if (app is BooketApplication) {
+            synchronized(this) {
+                if (!::activityProviders.isInitialized) {
+                    val appGraph = app.appGraph
+                    activityProviders = appGraph.activityProviders
+                    serviceProviders = appGraph.serviceProviders
+                }
+            }
+        }
         return app
     }
📝 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.

Suggested change
override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
val app = super.instantiateApplicationCompat(cl, className)
val appGraph = (app as BooketApplication).appGraph
activityProviders = appGraph.activityProviders
serviceProviders = appGraph.serviceProviders
return app
}
override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
val app = super.instantiateApplicationCompat(cl, className)
if (app is BooketApplication) {
synchronized(this) {
if (!::activityProviders.isInitialized) {
val appGraph = app.appGraph
activityProviders = appGraph.activityProviders
serviceProviders = appGraph.serviceProviders
}
}
}
return app
}

Comment on lines +4 to +10
import com.ninecraft.booket.core.di.DataScope
import com.ninecraft.booket.core.network.request.RefreshTokenRequest
import com.ninecraft.booket.core.network.service.ReedService
import com.orhanobut.logger.Logger
import dev.zacsweers.metro.Inject
import dev.zacsweers.metro.Provider
import dev.zacsweers.metro.SingleIn
Copy link

@coderabbitai coderabbitai bot Nov 21, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Metro DI 임포트 및 마이그레이션 확인 필요

Metro DI로의 마이그레이션이 올바르게 이루어진 것으로 보입니다. Provider 사용 패턴이 Metro의 함수형 호출 방식(serviceProvider())으로 정확히 적용되었습니다.

Metro 라이브러리의 최신 사용 패턴을 확인하여 @SingleIn, @Inject, Provider<T> 사용이 권장 방식과 일치하는지 검증해주세요:


🌐 Web query:

Metro dependency injection library Kotlin usage patterns @SingleIn @Inject Provider

💡 Result:

Summary of common Metro patterns for Kotlin DI (concise)

  • Scopes and @singlein

    • Use @singlein(MyScope::class) on an @Inject class or binding to make it a single instance per dependency graph/scope (like Dagger @singleton). [1][2]
  • Constructor injection with @Inject

    • Annotate constructors/classes with @Inject; Metro generates factories and wires them into @DependencyGraph accessors or @provides. Example:
      @Inject
      class Repo(private val api: Api)
    • Graphs are defined with @DependencyGraph and expose provision methods or properties. [1][3]
  • Provider and Lazy intrinsics

    • Inject Provider to obtain a new instance on each call (unless the binding is scoped — then Provider() returns the scoped instance). Inject Lazy (Kotlin’s Lazy) for first-time lazy initialization. Example:
      @Inject
      class Service(private val repoProvider: Provider) {
      fun newRepo() = repoProvider()
      } [4][5]
  • Multiplatform / optional bindings

    • Metro supports multiplatform, optional dependencies (default parameter values), Assisted injection, and multibindings. Use @ContributesBinding/@ContributesTo to aggregate platform-specific implementations with @singlein for scope. [2][3]
  • Graph creation and factories

    • Define graphs with @DependencyGraph and a nested @DependencyGraph.Factory when you need to pass runtime values via @provides in the factory. Create graphs via createGraph() or the generated factory. [1]
  • Practical tips

    • Use @singlein for lifecycle singletons (app/user scopes).
    • Use Provider when you need multiple instances or deferred creation; if the provided binding is scoped, Provider() returns the same scoped instance.
    • Prefer constructor @Inject for testability; put platform-specific implementations behind interfaces and contribute them with @ContributesBinding. [2][4]

Sources

  • Metro docs — Dependency graphs & API. [1]
  • Metro features & examples (scopes, @singlein, @ContributesBinding). [2]
  • Metro introduction / examples. [3]
  • Metro design doc — Provider / Lazy behavior. [4]
  • Amazon App Platform docs showing Metro + @singlein usage. [5]

주석 처리된 코드 제거 필요

Metro DI 마이그레이션이 올바르게 수행되었습니다. @SingleIn(DataScope::class), @Inject, Provider<T> 패턴 및 provider() 함수형 호출 방식이 Metro의 공식 권장사항과 정확히 일치합니다.

다음 부분의 주석 처리된 코드를 제거해주세요:

  • 22줄: serviceLazy 관련 주석 코드
  • 37줄: serviceProvider() 호출의 중복 주석 코드

이 부분들은 마이그레이션 과정에서 남겨진 데드 코드로, 정리가 필요합니다.

🤖 Prompt for AI Agents
In
core/network/src/main/kotlin/com/ninecraft/booket/core/network/TokenAuthenticator.kt
around lines 4 to 10: remove the leftover commented-out dead code from the Metro
DI migration—specifically delete the commented `serviceLazy` block at line 22
and the duplicated commented `serviceProvider()` call at line 37; ensure imports
remain used and clean up any now-unused import lines after removing those
comments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Provider, Lazy 둘다 빌드에 성공해서 둘중에 뭘 쓰는게 더 나은지 고민하고 있었어, 일단 기존에 Hilt에서 부터 사용했던게 Provider라 그대로 Provider로 옮긴거긴한데,,

Copy link

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@coderabbitai 의견 부탁해

Copy link

Choose a reason for hiding this comment

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

@easyhooon, 좋은 질문이에요! 두 가지 모두 유효한 선택이고, 현재 Provider를 사용한 구현도 완벽하게 동작합니다.

각 방식의 특징

Provider

  • 호출할 때마다 serviceProvider()를 실행하지만, ReedService가 스코프된 싱글톤이라면 매번 같은 인스턴스를 반환합니다
  • Hilt에서 사용하던 패턴과 동일하여 익숙함
  • 명시적인 함수 호출로 "지금 인스턴스를 가져온다"는 의도가 명확함

Lazy

  • 최초 접근 시 한 번만 초기화되고, 이후에는 캐시된 값을 반환합니다
  • 호출 시점이 더 간결함: serviceLazy.value
  • 지연 초기화(lazy initialization) 의도가 코드에서 더 명확하게 드러남
  • 미세하게 성능상 이점이 있음 (함수 호출 오버헤드 없음)

이 케이스에서의 추천

TokenAuthenticator의 경우, Lazy가 약간 더 적합해 보입니다:

  1. ReedService는 싱글톤이고, authenticate() 메서드에서 반복적으로 사용됨
  2. 순환 의존성을 방지하는 것이 주 목적 (Authenticator ↔ Service)
  3. 한 번 초기화 후 계속 같은 인스턴스를 사용하는 패턴이 더 명확함

하지만 현재 Provider 구현도 전혀 문제없습니다. ReedService가 스코프되어 있다면 실질적인 동작은 거의 동일하고, Hilt 패턴을 유지한다는 장점도 있습니다.

제안

  • 마이그레이션 일관성을 위해 현재 Provider를 유지하시거나
  • 좀 더 명확한 지연 초기화 의미를 원하신다면 Lazy로 변경

어느 쪽을 선택하시든, 결정 후에는 22줄과 37줄의 주석 처리된 코드를 정리해주세요! 😊

궁금한 점이 더 있으시면 말씀해주세요.

🐰 Provider든 Lazy든, 둘 다 당근처럼 훌륭한 선택이에요!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BOOK-434/refactor] Metro 적용

2 participants