Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ dependencies {

libs.kotlinx.collections.immutable,

platform(libs.firebase.bom),
libs.firebase.analytics,
libs.logger,
Comment on lines +29 to 31
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Firebase BoM + Analytics 추가 OK. KTX 전환 영향(확장 API 사용 여부)만 확인 부탁드립니다.

이번 변경으로 firebase-analytics-ktxfirebase-analytics로 전환되었습니다. 최신 BoM에서는 KTX 확장이 기본 아티팩트로 통합된 경우가 많지만, 코드에서 Firebase.analytics 등의 KTX 확장을 사용한다면 컴파일이 깨지지 않는지 점검이 필요합니다. 특히 AnalyticsHelper/AnalyticsModule에서 어떤 API를 쓰는지 확인해 주세요.

다음 스크립트로 KTX 확장 사용 흔적과 잔존 -ktx 의존성 유무를 빠르게 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# 1) KTX 확장 사용 여부 확인
rg -nP --type=kotlin -C2 '\bFirebaseAnalytics\.getInstance\(|\bFirebase\.analytics\b'

# 2) -ktx 아티팩트가 남아있는지 확인
rg -nP -g '!**/build/**' 'firebase-(analytics|crashlytics)-ktx'

Length of output: 746


KTX 확장 함수 의존성 누락으로 인한 컴파일 오류 위험

AnalyticsModule.kt에서 Firebase.analytics 확장 프로퍼티를 사용하고 있는데, firebase-analytics-ktx 의존성을 제거하면서 컴파일 오류가 발생할 수 있습니다. 다음 중 하나로 수정해 주세요:

  • core/common/build.gradle.kts
    • libs.firebase.analytics 대신 KTX 아티팩트인 libs.firebase.analytics.ktx를 추가
  • 또는 core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt (19행)
    • Firebase.analytics 대신 FirebaseAnalytics.getInstance(context) 호출로 변경
🤖 Prompt for AI Agents
In core/common/build.gradle.kts around lines 21 to 23, the KTX extension
dependency for Firebase Analytics was removed causing potential compile errors
in AnalyticsModule.kt where Firebase.analytics is used; fix by either replacing
libs.firebase.analytics with the KTX artifact libs.firebase.analytics.ktx in
this build file, or alternatively modify
core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt
at line 19 to stop using the Firebase.analytics extension and instead call
FirebaseAnalytics.getInstance(context) to obtain the analytics instance.

)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.ninecraft.booket.core.common.analytics

import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.logEvent
import com.orhanobut.logger.Logger
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AnalyticsHelper @Inject constructor(
private val firebaseAnalytics: FirebaseAnalytics,
) {

fun logScreenView(screenName: String) {
Logger.d("Analytics - Screen View: $screenName")
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.SCREEN_VIEW) {
param(FirebaseAnalytics.Param.SCREEN_NAME, screenName)
}
}

fun logEvent(eventName: String) {
Logger.d("Analytics - Event: $eventName")
firebaseAnalytics.logEvent(eventName) {}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.ninecraft.booket.core.common.analytics.di

import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.analytics
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

@Provides
@Singleton
fun provideFirebaseAnalytics(): FirebaseAnalytics {
return Firebase.analytics
}
}
Comment on lines +16 to +21
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 21, 2025

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Firebase.analytics 확장 프로퍼티 사용 시 KTX 의존성 확인 필요

Firebase.analytics는 KTX 확장에 의해 제공됩니다. 만약 의존성을 firebase-analytics(비-KTX)로만 교체했다면 컴파일이 실패할 수 있습니다. 두 가지 중 하나를 선택해 주세요.

옵션 A) KTX 유지

  • Gradle에 firebase-analytics-ktx가 포함되어 있는지 확인

검증 스크립트:

옵션 B) KTX 미사용 (대안 제공자)

  • 애플리케이션 컨텍스트 기반으로 인스턴스를 획득
import android.content.Context
import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
// import com.google.firebase.analytics.analytics // 제거
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

    @Provides
    @Singleton
    fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics =
        FirebaseAnalytics.getInstance(context)
}

팀 규칙(ktx 사용 여부)에 맞게 한 가지로 통일해 두면 혼선을 줄일 수 있습니다.


🏁 Script executed:

#!/bin/bash
rg -nP --glob '**/build.gradle.kts' 'firebase-analytics-ktx|libs\.firebase\.analyticsKtx'

Length of output: 91


스크립트가 build.gradle.kts에서 KTX 의존성을 찾지 못했습니다. Groovy DSL(.gradle) 또는 버전 카탈로그(.toml)에 정의됐을 수 있으므로, 전체 빌드 파일과 버전 카탈로그에서 firebase-analyticsfirebase-analytics-ktx 항목을 검색해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. 모든 Gradle 빌드 스크립트(.gradle, .gradle.kts)에서 firebase-analytics(-ktx) 의존성 검색
rg -nP --glob '**/build.gradle*' 'firebase-analytics(-ktx)?'
# 2. 버전 카탈로그(gradle/libs.versions.toml 등)에서 analytics 관련 항목 검색
rg -nP --glob '**/libs.versions.toml' 'analytics'

Length of output: 226


Firebase.analytics KTX 의존성 누락 확인 및 조치 필요

다음 사항을 확인했습니다.

  • gradle/libs.versions.toml(151행)에 firebase-analytics만 정의되어 있으며, firebase-analytics-ktx 관련 alias 또는 의존성은 없습니다.
  • 코드에서 Firebase.analytics 확장 프로퍼티를 사용하고 있으므로, KTX 모듈이 없으면 컴파일 에러가 발생합니다.

따라서 아래 두 가지 옵션 중 하나를 반드시 선택하여 반영해 주세요.

옵션 A) KTX 유지

Gradle 의존성에 KTX 모듈을 추가

// build.gradle.kts
dependencies {
    implementation("com.google.firebase:firebase-analytics-ktx")
}

또는 버전 카탈로그에 alias 등록

# gradle/libs.versions.toml
firebase-analytics-ktx = { group = "com.google.firebase", name = "firebase-analytics-ktx", version.ref = "firebase" }

옵션 B) KTX 미사용 (대안 제공자)

확장 프로퍼티 대신 애플리케이션 컨텍스트 기반으로 인스턴스 획득

import com.google.firebase.Firebase
import com.google.firebase.analytics.FirebaseAnalytics
// import com.google.firebase.analytics.analytics // 제거
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import android.content.Context

@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

    @Provides
    @Singleton
    fun provideFirebaseAnalytics(@ApplicationContext context: Context): FirebaseAnalytics =
        FirebaseAnalytics.getInstance(context)
}

위 두 옵션 중 팀 정책에 맞는 방식을 선택하여 일관성 있게 적용해 주세요.

🤖 Prompt for AI Agents
In core/common/src/main/kotlin/com/ninecraft/booket/core/common/analytics/di/AnalyticsModule.kt
around lines 16 to 21, the code uses the Firebase KTX extension property
Firebase.analytics but the Gradle version catalog does not include
firebase-analytics-ktx, causing a compile error; fix by either (A) adding the
KTX dependency to the project (add implementation
com.google.firebase:firebase-analytics-ktx or register an alias in
gradle/libs.versions.toml) so Firebase.analytics is available, or (B) stop using
the KTX extension and obtain FirebaseAnalytics via the application Context
(e.g., FirebaseAnalytics.getInstance(context)) and update the provider
accordingly; choose the team-approved option and apply it consistently across
build files and this provider.

1 change: 1 addition & 0 deletions core/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {
projects.core.common,

libs.compose.keyboard.state,
libs.compose.effects,
libs.logger,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
Expand All @@ -35,6 +34,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.ninecraft.booket.core.designsystem.theme.ReedTheme
import com.skydoves.compose.effects.RememberedEffect

// 기기에서 평균적으로 한 화면에 보이는 아이템 개수
private const val LIMIT_COUNT = 6
Expand Down Expand Up @@ -82,7 +82,7 @@ private fun LazyListState.onLoadMore(
}
}

LaunchedEffect(reached) {
RememberedEffect(reached) {
if (reached && layoutInfo.totalItemsCount > limitCount) action()
}
Comment on lines +85 to 87
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

무한 스크롤 트리거가 한 번만 실행되는 회귀 가능성: RememberedEffect(Boolean) 키 사용

RememberedEffect(reached)는 키 값이 동일하게 재방문될 때(예: true 재진입) 재실행되지 않습니다. 무한 스크롤에서는 스크롤 임계 도달 시마다 action()이 재호출되어야 하므로, boolean 키 기반의 RememberedEffect는 이후 페이지에서 트리거가 멈출 수 있습니다. LaunchedEffect + 키 변화 감지로 복구하는 것을 제안합니다.

아래처럼 교체하면 false → true 전이 때마다 한 번씩 호출됩니다.

-    RememberedEffect(reached) {
-        if (reached && layoutInfo.totalItemsCount > limitCount) action()
-    }
+    LaunchedEffect(reached) {
+        if (reached && layoutInfo.totalItemsCount > limitCount) {
+            action()
+        }
+    }

추가로, 더 견고하게 하려면 snapshotFlow로 전이를 명시적으로 다루는 방식도 가능합니다(선택):

// 추가 import: import kotlinx.coroutines.flow.*
LaunchedEffect(limitCount, loadOnBottom) {
  snapshotFlow { reachedBottom(limitCount, loadOnBottom) to layoutInfo.totalItemsCount }
    .distinctUntilChanged() // (reached, count) 변화만 반응
    .filter { (reached, count) -> reached && count > limitCount }
    .collect { action() }
}

필요 시 isLoading 플래그를 인자로 받아 중복 호출을 더 강하게 차단하는 것도 고려해 주세요.

🤖 Prompt for AI Agents
In
core/ui/src/main/kotlin/com/ninecraft/booket/core/ui/component/InfinityLazyColumn.kt
around lines 85 to 87, the RememberedEffect(reached) usage will not re-run when
the boolean key remains the same on re-entry, causing infinite-scroll triggers
to stop after the first hit; replace it with a LaunchedEffect keyed on relevant
inputs (e.g., limitCount and loadOnBottom) that observes transitions or uses
snapshotFlow to detect reached→true transitions (optionally include
layoutInfo.totalItemsCount in the observed tuple), filter for reached &&
totalItemsCount > limitCount and then call action(); also consider passing an
isLoading flag to prevent duplicate calls.

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.constants.BookStatus
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.BookRepository
Expand All @@ -28,6 +29,7 @@ import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuitx.effects.ImpressionEffect
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -46,10 +48,13 @@ class BookDetailPresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
private val bookRepository: BookRepository,
private val recordRepository: RecordRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<BookDetailUiState> {
companion object {
private const val PAGE_SIZE = 20
private const val START_INDEX = 0
private const val BOOK_DELETE = "library_book_delete"
private const val BOOK_DELETE_COMPLETE = "library_book_delete_complete"
}

private fun getRecordComparator(sortType: RecordSort): Comparator<ReadingRecordModel> {
Expand Down Expand Up @@ -213,6 +218,7 @@ class BookDetailPresenter @AssistedInject constructor(
scope.launch {
bookRepository.deleteBook(userBookId = userBookId)
.onSuccess {
analyticsHelper.logEvent(BOOK_DELETE_COMPLETE)
onSuccess()
}
.onFailure { exception ->
Expand Down Expand Up @@ -328,6 +334,7 @@ class BookDetailPresenter @AssistedInject constructor(
is BookDetailUiEvent.OnDeleteRecordClick -> {
isRecordMenuBottomSheetVisible = false
isRecordDeleteDialogVisible = true
analyticsHelper.logEvent(BOOK_DELETE)
}

is BookDetailUiEvent.OnDeleteRecord -> {
Expand Down Expand Up @@ -387,6 +394,10 @@ class BookDetailPresenter @AssistedInject constructor(
}
}

ImpressionEffect {
analyticsHelper.logScreenView(screen.name)
}
Comment on lines +397 to +399
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스크린 뷰 로깅 표준화 권장

여기도 Helper를 screen_view + 파라미터 방식으로 바꾸면 대시보드와 일관되게 연동됩니다.

🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/book/BookDetailPresenter.kt
around lines 389 to 391, the ImpressionEffect currently calls
analyticsHelper.logScreenView(screen.name); change this to use the standardized
event format (event name "screen_view" with a parameters map) so it matches
dashboard telemetry; construct a params map including the screen name (e.g.,
mapOf("screen_name" to screen.name) and any other relevant fields, then call
analyticsHelper.logEvent("screen_view", params) (or the project's equivalent
helper signature) to keep logging consistent.


return BookDetailUiState(
uiState = uiState,
footerState = footerState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.feature.screens.RecordCardScreen
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuitx.effects.ImpressionEffect
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -17,7 +19,14 @@ import dagger.hilt.android.components.ActivityRetainedComponent
class RecordCardPresenter @AssistedInject constructor(
@Assisted private val screen: RecordCardScreen,
@Assisted private val navigator: Navigator,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<RecordCardUiState> {

companion object {
private const val RECORD_CARD_SAVE = "record_card_save"
private const val RECORD_CARD_SHARE = "record_card_share"
}

@Composable
override fun present(): RecordCardUiState {
var isLoading by rememberRetained { mutableStateOf(false) }
Expand Down Expand Up @@ -45,16 +54,22 @@ class RecordCardPresenter @AssistedInject constructor(

is RecordCardUiEvent.SaveRecordCard -> {
isCapturing = false
analyticsHelper.logEvent(RECORD_CARD_SAVE)
sideEffect = RecordCardSideEffect.SaveImage(event.bitmap)
}

is RecordCardUiEvent.ShareRecordCard -> {
isSharing = false
analyticsHelper.logEvent(RECORD_CARD_SHARE)
sideEffect = RecordCardSideEffect.ShareImage(event.bitmap)
}
}
}

ImpressionEffect {
analyticsHelper.logScreenView(screen.name)
}

return RecordCardUiState(
isLoading = isLoading,
quote = screen.quote,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.ninecraft.booket.feature.detail.record

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.RecordRepository
import com.ninecraft.booket.core.model.RecordDetailModel
Expand All @@ -16,10 +16,12 @@ import com.ninecraft.booket.feature.screens.RecordEditScreen
import com.ninecraft.booket.feature.screens.arguments.RecordEditArgs
import com.ninecraft.booket.feature.screens.extensions.delayedGoTo
import com.orhanobut.logger.Logger
import com.skydoves.compose.effects.RememberedEffect
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

RememberedEffect 의존성 확인 필요

com.skydoves.compose.effects.RememberedEffect를 사용 중입니다. 모듈 의존성이 추가되어 있는지 확인 부탁드립니다. 없으면 빌드 오류가 발생합니다. 없을 경우 LaunchedEffect(Unit)로 대체해도 무방합니다.

아래 스크립트로 Gradle 설정에 의존성이 존재하는지 확인하세요:

Also applies to: 160-160


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 프로젝트 내 Gradle/TOML에서 compose-effects 또는 skydoves 의존성 검색
rg -n -C2 -S 'compose-effects|skydoves' -g '*.{gradle,gradle.kts,toml}'

Length of output: 2034


feature/detail 모듈의 Gradle 설정에서 compose-effects 의존성 추가 여부를 확인해주세요.
현재 gradle/libs.versions.toml에 alias 정의(compose-effects = { group = "com.github.skydoves", name = "compose-effects", … })는 되어 있으나, 해당 모듈의 build.gradle[.kts]에서 실제로 참조하고 있는지 확인이 필요합니다.

아래 스크립트를 실행하고 결과를 공유해주세요:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# feature/detail 모듈의 build.gradle(.kts)에서 compose-effects 의존성 사용 확인
rg -n 'compose-effects' -g 'feature/detail/**/build.gradle*'

Length of output: 80


feature/detail 모듈이 version catalog alias(libs.compose-effects)로 의존성을 참조하는지 확인이 필요합니다. 아래 스크립트를 실행 후 결과를 공유해주세요.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# feature/detail 모듈의 build.gradle(.kts)에서 version catalog alias로 compose-effects 사용 확인
rg -n 'libs\.compose-effects' -g 'feature/detail/**/build.gradle*'

Length of output: 88


feature/detail 모듈에 compose-effects 의존성 추가 필요

RecordDetailPresenter.kt에서

import com.skydoves.compose.effects.RememberedEffect

를 사용 중이나, feature/detail 모듈의 Gradle 설정에 해당 의존성이 정의되어 있지 않습니다.

해결 방법:

  • feature/detail/build.gradle(.kts)dependencies 블록에 아래 중 하나를 추가해주세요.

    • 버전 카탈로그(alias) 사용 시
      implementation(libs.composeEffects)
    • 직접 추가 시
      implementation("com.github.skydoves:compose-effects:0.1.1")
  • 만약 RememberedEffect 대신 표준 Compose API를 사용하려면,

    LaunchedEffect(Unit) { … }

    로 교체 가능합니다.

🤖 Prompt for AI Agents
In
feature/detail/src/main/kotlin/com/ninecraft/booket/feature/detail/record/RecordDetailPresenter.kt
around line 17, the import com.skydoves.compose.effects.RememberedEffect is used
but the feature/detail module lacks the compose-effects dependency; add the
dependency to feature/detail/build.gradle(.kts) by either adding the catalog
alias implementation(libs.composeEffects) or
implementation("com.github.skydoves:compose-effects:0.1.1"), or alternatively
replace the RememberedEffect usage with the standard Compose API (e.g.,
LaunchedEffect(Unit) { … }) and remove the skydoves import.

import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuitx.effects.ImpressionEffect
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -30,8 +32,14 @@ class RecordDetailPresenter @AssistedInject constructor(
@Assisted private val screen: RecordDetailScreen,
@Assisted private val navigator: Navigator,
private val repository: RecordRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<RecordDetailUiState> {

companion object {
private const val RECORD_DELETE = "record_delete"
private const val RECORD_DELETE_COMPLETE = "record_delete_complete"
}

@Composable
override fun present(): RecordDetailUiState {
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -72,6 +80,7 @@ class RecordDetailPresenter @AssistedInject constructor(
scope.launch {
repository.deleteRecord(readingRecordId = readingRecordId)
.onSuccess {
analyticsHelper.logEvent(RECORD_DELETE_COMPLETE)
onSuccess()
}
.onFailure { exception ->
Expand Down Expand Up @@ -146,6 +155,7 @@ class RecordDetailPresenter @AssistedInject constructor(
}

is RecordDetailUiEvent.OnDeleteRecordClick -> {
analyticsHelper.logEvent(RECORD_DELETE)
isRecordMenuBottomSheetVisible = false
isRecordDeleteDialogVisible = true
}
Expand All @@ -162,10 +172,14 @@ class RecordDetailPresenter @AssistedInject constructor(
}
}

LaunchedEffect(Unit) {
RememberedEffect(Unit) {
getRecordDetail(screen.recordId)
}

ImpressionEffect {
analyticsHelper.logScreenView(screen.name)
}

return RecordDetailUiState(
uiState = uiState,
recordDetailInfo = recordDetailInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.common.utils.handleException
import com.ninecraft.booket.core.data.api.repository.RecordRepository
import com.ninecraft.booket.feature.screens.EmotionEditScreen
Expand All @@ -20,6 +21,7 @@ import com.slack.circuit.foundation.rememberAnsweringNavigator
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuitx.effects.ImpressionEffect
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -30,8 +32,15 @@ class RecordEditPresenter @AssistedInject constructor(
@Assisted private val screen: RecordEditScreen,
@Assisted private val navigator: Navigator,
private val repository: RecordRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<RecordEditUiState> {

companion object {
private const val MAX_PAGE = 4032
private const val RECORD_EDIT = "record_edit_save"
private const val RECORD_EDIT_SAVE = "record_edit_save"
}
Comment on lines +38 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

스크린 뷰 이벤트 키가 잘못됨(복사-붙여넣기 오류로 추정) + 스크린 이름 직접 사용 권장

RECORD_EDIT 상수가 "record_edit_save"로 설정되어 있어 스크린 뷰가 저장 이벤트와 같은 이름으로 기록됩니다. 로그인 화면처럼 RecordEditScreen.name을 직접 사용하면 일관성이 생기고 실수를 줄일 수 있습니다. 또한 불필요한 상수는 제거하세요.

적용 Diff:

     companion object {
         private const val MAX_PAGE = 4032
-        private const val RECORD_EDIT = "record_edit_save"
         private const val RECORD_EDIT_SAVE = "record_edit_save"
     }
@@
-        ImpressionEffect {
-            analyticsHelper.logScreenView(RECORD_EDIT)
-        }
+        ImpressionEffect {
+            analyticsHelper.logScreenView(RecordEditScreen.name)
+        }

Also applies to: 146-149

🤖 Prompt for AI Agents
In
feature/edit/src/main/kotlin/com/ninecraft/booket/feature/edit/record/RecordEditPresenter.kt
around lines 38-42 and 146-149, the RECORD_EDIT constant is incorrectly set to
"record_edit_save" (likely a copy-paste error) and duplicates the save-event
constant; replace usages of RECORD_EDIT to use RecordEditScreen.name directly
for screen view reporting, remove the unnecessary RECORD_EDIT constant, and
ensure only a single RECORD_EDIT_SAVE (or appropriately named save constant)
remains for the save event to avoid name collision.


@Composable
override fun present(): RecordEditUiState {
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -85,6 +94,7 @@ class RecordEditPresenter @AssistedInject constructor(
emotionTags = emotionTags,
review = impression,
).onSuccess {
analyticsHelper.logEvent(RECORD_EDIT_SAVE)
onSuccess()
}.onFailure { exception ->
val handleErrorMessage = { message: String ->
Expand Down Expand Up @@ -133,6 +143,10 @@ class RecordEditPresenter @AssistedInject constructor(
}
}

ImpressionEffect {
analyticsHelper.logScreenView(RECORD_EDIT)
}

return RecordEditUiState(
recordInfo = recordInfo,
recordPageState = recordPageState,
Expand All @@ -153,8 +167,4 @@ class RecordEditPresenter @AssistedInject constructor(
navigator: Navigator,
): RecordEditPresenter
}

companion object {
const val MAX_PAGE = 4032
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
package com.ninecraft.booket.feature.home

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.ninecraft.booket.core.common.analytics.AnalyticsHelper
import com.ninecraft.booket.core.data.api.repository.BookRepository
import com.ninecraft.booket.core.model.RecentBookModel
import com.ninecraft.booket.feature.screens.BookDetailScreen
import com.ninecraft.booket.feature.screens.HomeScreen
import com.ninecraft.booket.feature.screens.RecordScreen
import com.ninecraft.booket.feature.screens.SearchScreen
import com.ninecraft.booket.feature.screens.SettingsScreen
import com.skydoves.compose.effects.RememberedEffect
import com.slack.circuit.codegen.annotations.CircuitInject
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.Navigator
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuitx.effects.ImpressionEffect
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -28,6 +30,7 @@ import kotlinx.coroutines.launch
class HomePresenter @AssistedInject constructor(
@Assisted private val navigator: Navigator,
private val repository: BookRepository,
private val analyticsHelper: AnalyticsHelper,
) : Presenter<HomeUiState> {

@Composable
Expand Down Expand Up @@ -85,10 +88,14 @@ class HomePresenter @AssistedInject constructor(
}
}

LaunchedEffect(true) {
RememberedEffect(true) {
loadHomeContent()
}

ImpressionEffect {
analyticsHelper.logScreenView(HomeScreen.name)
}
Comment on lines +95 to +97
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

스크린 뷰 로깅 방식을 GA4 표준으로 통일 권장

ImpressionEffect 사용은 적절합니다. 다만 AnalyticsHelper.logScreenView가 이벤트명을 스크린명으로 보내지 않도록 Helper 구현을 screen_view + screen_name, screen_class 파라미터 방식으로 전환해 주세요. 리포팅 정합성에 직접 영향이 있습니다.

🤖 Prompt for AI Agents
In
feature/home/src/main/kotlin/com/ninecraft/booket/feature/home/HomePresenter.kt
around lines 95–97, the current ImpressionEffect calls
analyticsHelper.logScreenView(HomeScreen.name) which sends the screen name as an
event; change it to send a GA4-standard "screen_view" event with parameters:
"screen_name" set to the screen's name and "screen_class" set to the screen's
class identifier. Update the call to use the AnalyticsHelper API that accepts an
event name and parameters (or add such an overload) so the ImpressionEffect logs
event "screen_view" with a params map containing "screen_name" ->
HomeScreen.name and "screen_class" -> the screen class name.


return HomeUiState(
uiState = uiState,
recentBooks = recentBooks,
Expand Down
Loading
Loading