From 8d938dfc1fc249c392a353505cde2a2dfc0cccdc Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Fri, 28 Aug 2026 21:14:36 +0900 Subject: [PATCH 01/12] =?UTF-8?q?Feat:=20Meta=20App=20Events=20SDK=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99=20=EB=B0=8F=20=ED=91=9C=EC=A4=80=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=9E=90=EB=8F=99=20=EC=88=98=EC=A7=91=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 23 +++++++++++++++++++ app/src/main/AndroidManifest.xml | 6 +++++ .../threegap/bitnagil/BitnagilApplication.kt | 10 ++++++++ gradle/libs.versions.toml | 2 ++ 4 files changed, 41 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07a3b8b6..b817988b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,6 +65,16 @@ android { ?: System.getenv("BITNAGIL_DEV_URL") ?: throw GradleException("bitnagil.dev.url 값이 없습니다.") buildConfigField("String", "BASE_URL", "\"$devUrl\"") + + val facebookDebugAppId = properties["facebook.debug.app.id"] as? String + ?: System.getenv("FACEBOOK_DEBUG_APP_ID") + ?: throw GradleException("facebook.debug.app.id 값이 없습니다.") + val facebookDebugClientToken = properties["facebook.debug.client.token"] as? String + ?: System.getenv("FACEBOOK_DEBUG_CLIENT_TOKEN") + ?: throw GradleException("facebook.debug.client.token 값이 없습니다.") + + resValue("string", "facebook_app_id", facebookDebugAppId) + resValue("string", "facebook_client_token", facebookDebugClientToken) } release { @@ -72,6 +82,17 @@ android { ?: System.getenv("BITNAGIL_PROD_URL") ?: throw GradleException("bitnagil.prod.url 값이 없습니다.") buildConfigField("String", "BASE_URL", "\"$prodUrl\"") + + val facebookAppId = properties["facebook.app.id"] as? String + ?: System.getenv("FACEBOOK_APP_ID") + ?: throw GradleException("facebook.app.id 값이 없습니다.") + val facebookClientToken = properties["facebook.client.token"] as? String + ?: System.getenv("FACEBOOK_CLIENT_TOKEN") + ?: throw GradleException("facebook.client.token 값이 없습니다.") + + resValue("string", "facebook_app_id", facebookAppId) + resValue("string", "facebook_client_token", facebookClientToken) + isMinifyEnabled = true isShrinkResources = true proguardFiles( @@ -84,6 +105,7 @@ android { buildFeatures { buildConfig = true + resValues = true } } @@ -97,6 +119,7 @@ dependencies { implementation(projects.presentation) implementation(libs.kakao.v2.user) + implementation(libs.facebook.core) implementation(platform(libs.retrofit.bom)) implementation(libs.bundles.retrofit) implementation(platform(libs.okhttp.bom)) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9c0e6200..b17a6d00 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,5 +57,11 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + + + + + diff --git a/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt b/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt index 392de4eb..d26d74f8 100644 --- a/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt +++ b/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt @@ -9,6 +9,8 @@ import coil3.memory.MemoryCache import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.request.crossfade import coil3.util.DebugLogger +import com.facebook.FacebookSdk +import com.facebook.LoggingBehavior import com.kakao.sdk.common.KakaoSdk import com.threegap.bitnagil.di.core.CoilEntryPoint import dagger.hilt.EntryPoints @@ -21,6 +23,7 @@ class BitnagilApplication : Application(), SingletonImageLoader.Factory { override fun onCreate() { super.onCreate() initKakaoSdk() + configureMetaAppEvents() } override fun newImageLoader(context: PlatformContext): ImageLoader { @@ -49,4 +52,11 @@ class BitnagilApplication : Application(), SingletonImageLoader.Factory { private fun initKakaoSdk() { KakaoSdk.init(this, BuildConfig.KAKAO_NATIVE_APP_KEY) } + + private fun configureMetaAppEvents() { + if (BuildConfig.DEBUG) { + FacebookSdk.setIsDebugEnabled(true) + FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS) + } + } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 24d362c8..69dd8c29 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -61,6 +61,7 @@ lottie-compose = "6.7.1" coil = "3.4.0" accompanist = "0.37.3" playServicesLocation = "21.3.0" +facebookSdk = "18.3.0" [libraries] ## Android Gradle Plugin @@ -140,6 +141,7 @@ lottie-compose = { group = "com.airbnb.android", name = "lottie-compose", versio accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } play-services-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "playServicesLocation" } +facebook-core = { group = "com.facebook.android", name = "facebook-core", version.ref = "facebookSdk" } [bundles] From dbf911fafaa0dd7154cf1bf0bb5d0a53ad897a3b Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Fri, 28 Aug 2026 21:44:21 +0900 Subject: [PATCH 02/12] =?UTF-8?q?Feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=EC=8B=9C=20Meta=20App=20Events=20?= =?UTF-8?q?=EC=98=A8=EB=B3=B4=EB=94=A9=20=EC=99=84=EB=A3=8C=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../di/presentation/AnalyticsLoggerModule.kt | 17 +++++++++++++++ .../util/analytics/MetaAnalyticsLogger.kt | 21 +++++++++++++++++++ .../screen/onboarding/OnBoardingViewModel.kt | 4 ++++ .../util/analytics/AnalyticsLogger.kt | 8 +++++++ 4 files changed, 50 insertions(+) create mode 100644 app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt create mode 100644 app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt diff --git a/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt b/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt new file mode 100644 index 00000000..40d45f8e --- /dev/null +++ b/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt @@ -0,0 +1,17 @@ +package com.threegap.bitnagil.di.presentation + +import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger +import com.threegap.bitnagil.util.analytics.MetaAnalyticsLogger +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class AnalyticsLoggerModule { + @Binds + @Singleton + abstract fun bindAnalyticsLogger(impl: MetaAnalyticsLogger): AnalyticsLogger +} diff --git a/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt b/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt new file mode 100644 index 00000000..22f01950 --- /dev/null +++ b/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt @@ -0,0 +1,21 @@ +package com.threegap.bitnagil.util.analytics + +import android.content.Context +import com.facebook.appevents.AppEventsConstants +import com.facebook.appevents.AppEventsLogger +import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger +import dagger.hilt.android.qualifiers.ApplicationContext +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class MetaAnalyticsLogger @Inject constructor( + @ApplicationContext private val context: Context, +) : AnalyticsLogger { + + private val logger by lazy { AppEventsLogger.newLogger(context) } + + override fun logOnBoardingCompleted() { + logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_REGISTRATION) + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt index 03f54a07..a73fb9df 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt @@ -16,6 +16,7 @@ import com.threegap.bitnagil.presentation.screen.onboarding.model.OnBoardingPage import com.threegap.bitnagil.presentation.screen.onboarding.model.OnBoardingSetType import com.threegap.bitnagil.presentation.screen.onboarding.model.navarg.OnBoardingScreenArg import com.threegap.bitnagil.presentation.screen.onboarding.model.toUiModel +import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -36,6 +37,7 @@ class OnBoardingViewModel @AssistedInject constructor( private val registerRecommendOnBoardingRoutinesUseCase: RegisterRecommendOnBoardingRoutinesUseCase, private val getUserProfileUseCase: GetUserProfileUseCase, private val getUserOnBoardingUseCase: GetUserOnBoardingUseCase, + private val analyticsLogger: AnalyticsLogger, @Assisted private val onBoardingArg: OnBoardingScreenArg, ) : ContainerHost, ViewModel() { @AssistedFactory interface Factory { @@ -344,6 +346,7 @@ class OnBoardingViewModel @AssistedInject constructor( registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds = selectedRoutineIds).fold( onSuccess = { _ -> + analyticsLogger.logOnBoardingCompleted() postSideEffect(sideEffect = OnBoardingSideEffect.NavigateToHomeScreen) }, onFailure = { @@ -352,6 +355,7 @@ class OnBoardingViewModel @AssistedInject constructor( } fun skipRegisterRecommendRoutines() = intent { + analyticsLogger.logOnBoardingCompleted() postSideEffect(sideEffect = OnBoardingSideEffect.NavigateToHomeScreen) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt new file mode 100644 index 00000000..13e636c0 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt @@ -0,0 +1,8 @@ +package com.threegap.bitnagil.presentation.util.analytics + +interface AnalyticsLogger { + /** + * 온보딩 완료를 기록한다. 추천 루틴을 등록했든 건너뛰었든 동일하게 기록한다. + */ + fun logOnBoardingCompleted() +} From c1ef5f304b76829301eea1b78e42e5e8a27ee618 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 17:31:44 +0900 Subject: [PATCH 03/12] =?UTF-8?q?Chore:=20Gradle=20=EB=B3=91=EB=A0=AC=20sy?= =?UTF-8?q?nc=20=EC=98=B5=EC=85=98=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle.properties b/gradle.properties index 5f7af421..d81a9e27 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,3 +7,6 @@ kotlin.daemon.jvmargs=-Xmx2g -XX:+UseParallelGC kotlin.code.style=official android.useAndroidX=true android.nonTransitiveRClass=true + +# Enabled parallel sync for Gradle 9.4+ +org.gradle.tooling.parallel=true From 6fecb139cb23bb70b3210e4b77ac525f4ed7c066 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 17:31:48 +0900 Subject: [PATCH 04/12] =?UTF-8?q?Refactor:=20local.properties=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=A1=9C=EC=A7=81=EC=9D=84=20build-logic=20?= =?UTF-8?q?=ED=99=95=EC=9E=A5=20=ED=95=A8=EC=88=98=EB=A1=9C=20=ED=86=B5?= =?UTF-8?q?=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 59 +++++-------------- .../convention/extension/LocalProperties.kt | 42 +++++++++++++ 2 files changed, 56 insertions(+), 45 deletions(-) create mode 100644 build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b817988b..97a2c8f2 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,4 +1,5 @@ -import java.util.Properties +import com.threegap.bitnagil.convention.extension.propertyOrNull +import com.threegap.bitnagil.convention.extension.requireProperty plugins { alias(libs.plugins.bitnagil.android.application) @@ -7,34 +8,17 @@ plugins { } android { - val properties = - Properties().apply { - val propFile = rootProject.file("local.properties") - if (propFile.exists()) { - load(propFile.inputStream()) - } - } - signingConfigs { create("release") { - keyAlias = properties["release.key.alias"] as? String - ?: System.getenv("RELEASE_KEY_ALIAS") - ?: throw GradleException("RELEASE_KEY_ALIAS 값이 없습니다.") - keyPassword = properties["release.key.password"] as? String - ?: System.getenv("RELEASE_KEY_PASSWORD") - ?: throw GradleException("RELEASE_KEY_PASSWORD 값이 없습니다.") - storePassword = properties["release.keystore.password"] as? String - ?: System.getenv("RELEASE_KEYSTORE_PASSWORD") - ?: throw GradleException("RELEASE_KEYSTORE_PASSWORD 값이 없습니다.") - storeFile = File("${properties["release.keystore.path"]}") + keyAlias = requireProperty("release.key.alias", "RELEASE_KEY_ALIAS") + keyPassword = requireProperty("release.key.password", "RELEASE_KEY_PASSWORD") + storePassword = requireProperty("release.keystore.password", "RELEASE_KEYSTORE_PASSWORD") + storeFile = File("${propertyOrNull("release.keystore.path", "RELEASE_KEYSTORE_PATH")}") } } defaultConfig { - val kakaoNativeAppKey = - (properties["kakao.native.app.key"] as? String) - ?: System.getenv("KAKAO_NATIVE_APP_KEY") - ?: throw GradleException("KAKAO_NATIVE_APP_KEY 값이 없습니다.") + val kakaoNativeAppKey = requireProperty("kakao.native.app.key", "KAKAO_NATIVE_APP_KEY") manifestPlaceholders["KAKAO_NATIVE_APP_KEY"] = kakaoNativeAppKey buildConfigField( @@ -43,10 +27,7 @@ android { value = "\"$kakaoNativeAppKey\"", ) - val kakaoRestApiKey = - (properties["kakao.rest.api.key"] as? String) - ?: System.getenv("KAKAO_REST_API_KEY") - ?: throw GradleException("KAKAO_REST_API_KEY 값이 없습니다.") + val kakaoRestApiKey = requireProperty("kakao.rest.api.key", "KAKAO_REST_API_KEY") buildConfigField( type = "String", @@ -61,34 +42,22 @@ android { versionNameSuffix = "-DEBUG" isDebuggable = true - val devUrl = properties["bitnagil.dev.url"] as? String - ?: System.getenv("BITNAGIL_DEV_URL") - ?: throw GradleException("bitnagil.dev.url 값이 없습니다.") + val devUrl = requireProperty("bitnagil.dev.url", "BITNAGIL_DEV_URL") buildConfigField("String", "BASE_URL", "\"$devUrl\"") - val facebookDebugAppId = properties["facebook.debug.app.id"] as? String - ?: System.getenv("FACEBOOK_DEBUG_APP_ID") - ?: throw GradleException("facebook.debug.app.id 값이 없습니다.") - val facebookDebugClientToken = properties["facebook.debug.client.token"] as? String - ?: System.getenv("FACEBOOK_DEBUG_CLIENT_TOKEN") - ?: throw GradleException("facebook.debug.client.token 값이 없습니다.") + val facebookDebugAppId = requireProperty("facebook.debug.app.id", "FACEBOOK_DEBUG_APP_ID") + val facebookDebugClientToken = requireProperty("facebook.debug.client.token", "FACEBOOK_DEBUG_CLIENT_TOKEN") resValue("string", "facebook_app_id", facebookDebugAppId) resValue("string", "facebook_client_token", facebookDebugClientToken) } release { - val prodUrl = properties["bitnagil.prod.url"] as? String - ?: System.getenv("BITNAGIL_PROD_URL") - ?: throw GradleException("bitnagil.prod.url 값이 없습니다.") + val prodUrl = requireProperty("bitnagil.prod.url", "BITNAGIL_PROD_URL") buildConfigField("String", "BASE_URL", "\"$prodUrl\"") - val facebookAppId = properties["facebook.app.id"] as? String - ?: System.getenv("FACEBOOK_APP_ID") - ?: throw GradleException("facebook.app.id 값이 없습니다.") - val facebookClientToken = properties["facebook.client.token"] as? String - ?: System.getenv("FACEBOOK_CLIENT_TOKEN") - ?: throw GradleException("facebook.client.token 값이 없습니다.") + val facebookAppId = requireProperty("facebook.app.id", "FACEBOOK_APP_ID") + val facebookClientToken = requireProperty("facebook.client.token", "FACEBOOK_CLIENT_TOKEN") resValue("string", "facebook_app_id", facebookAppId) resValue("string", "facebook_client_token", facebookClientToken) diff --git a/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt b/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt new file mode 100644 index 00000000..4e601beb --- /dev/null +++ b/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt @@ -0,0 +1,42 @@ +package com.threegap.bitnagil.convention.extension + +import java.util.Properties +import org.gradle.api.GradleException +import org.gradle.api.Project + +private const val LOCAL_PROPERTIES_FILE_NAME = "local.properties" +private const val LOCAL_PROPERTIES_EXTRA_KEY = "bitnagilLocalProperties" + +private fun Project.loadLocalProperties(): Properties = + Properties().apply { + val localPropertiesFile = rootProject.file(LOCAL_PROPERTIES_FILE_NAME) + if (localPropertiesFile.exists()) { + localPropertiesFile.inputStream().use(::load) + } + } + +/** + * 모듈마다 한 번만 읽고 해당 프로젝트의 extra 에 담아둔다. + * 데몬이 아니라 빌드마다 새로 만들어지는 저장소이므로 local.properties 를 수정하면 다음 빌드에 반영된다. + */ +private val Project.localProperties: Properties + get() { + val extra = extensions.extraProperties + if (!extra.has(LOCAL_PROPERTIES_EXTRA_KEY)) { + extra.set(LOCAL_PROPERTIES_EXTRA_KEY, loadLocalProperties()) + } + return extra.get(LOCAL_PROPERTIES_EXTRA_KEY) as Properties + } + +/** + * local.properties 값을 읽고, 없으면 환경 변수를 사용한다. 둘 다 없으면 null 을 반환한다. + */ +fun Project.propertyOrNull(propertyKey: String, environmentKey: String): String? = + localProperties.getProperty(propertyKey) ?: System.getenv(environmentKey) + +/** + * [propertyOrNull] 과 동일하되, 값이 없으면 설정 단계에서 실패한다. + */ +fun Project.requireProperty(propertyKey: String, environmentKey: String): String = + propertyOrNull(propertyKey, environmentKey) + ?: throw GradleException("$propertyKey 값이 없습니다.") From 8c8539ad69e1074c601bc19dd06a2ae62cc39a1e Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 17:32:14 +0900 Subject: [PATCH 05/12] =?UTF-8?q?Refactor:=20=EB=B6=84=EC=84=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20core:analytics=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AnalyticsLogger 인터페이스와 Meta 구현체를 core:analytics 로 이동 - Meta SDK 의존성과 자격증명 resValue, 매니페스트 meta-data 를 해당 모듈이 소유 - Hilt 애노테이션을 걷어내고 다른 core 모듈과 동일하게 app 의 di/core 에서 조립 - FacebookSdk 디버그 로깅 설정을 MetaAnalyticsInitializer 로 캡슐화 --- app/build.gradle.kts | 15 +------ app/src/main/AndroidManifest.xml | 2 - .../threegap/bitnagil/BitnagilApplication.kt | 8 +--- .../bitnagil/di/core/AnalyticsModule.kt | 21 +++++++++ .../di/presentation/AnalyticsLoggerModule.kt | 17 -------- .../util/analytics/MetaAnalyticsLogger.kt | 21 --------- core/analytics/build.gradle.kts | 43 +++++++++++++++++++ core/analytics/src/main/AndroidManifest.xml | 8 ++++ .../bitnagil}/analytics/AnalyticsLogger.kt | 2 +- .../meta/MetaAnalyticsInitializer.kt | 13 ++++++ .../analytics/meta/MetaAnalyticsLogger.kt | 15 +++++++ presentation/build.gradle.kts | 1 + .../screen/onboarding/OnBoardingViewModel.kt | 2 +- settings.gradle.kts | 1 + 14 files changed, 107 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/com/threegap/bitnagil/di/core/AnalyticsModule.kt delete mode 100644 app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt delete mode 100644 app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt create mode 100644 core/analytics/build.gradle.kts create mode 100644 core/analytics/src/main/AndroidManifest.xml rename {presentation/src/main/java/com/threegap/bitnagil/presentation/util => core/analytics/src/main/java/com/threegap/bitnagil}/analytics/AnalyticsLogger.kt (76%) create mode 100644 core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsInitializer.kt create mode 100644 core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 97a2c8f2..993b6b70 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -44,24 +44,12 @@ android { val devUrl = requireProperty("bitnagil.dev.url", "BITNAGIL_DEV_URL") buildConfigField("String", "BASE_URL", "\"$devUrl\"") - - val facebookDebugAppId = requireProperty("facebook.debug.app.id", "FACEBOOK_DEBUG_APP_ID") - val facebookDebugClientToken = requireProperty("facebook.debug.client.token", "FACEBOOK_DEBUG_CLIENT_TOKEN") - - resValue("string", "facebook_app_id", facebookDebugAppId) - resValue("string", "facebook_client_token", facebookDebugClientToken) } release { val prodUrl = requireProperty("bitnagil.prod.url", "BITNAGIL_PROD_URL") buildConfigField("String", "BASE_URL", "\"$prodUrl\"") - val facebookAppId = requireProperty("facebook.app.id", "FACEBOOK_APP_ID") - val facebookClientToken = requireProperty("facebook.client.token", "FACEBOOK_CLIENT_TOKEN") - - resValue("string", "facebook_app_id", facebookAppId) - resValue("string", "facebook_client_token", facebookClientToken) - isMinifyEnabled = true isShrinkResources = true proguardFiles( @@ -74,11 +62,11 @@ android { buildFeatures { buildConfig = true - resValues = true } } dependencies { + implementation(projects.core.analytics) implementation(projects.core.datastore) implementation(projects.core.designsystem) implementation(projects.core.network) @@ -88,7 +76,6 @@ dependencies { implementation(projects.presentation) implementation(libs.kakao.v2.user) - implementation(libs.facebook.core) implementation(platform(libs.retrofit.bom)) implementation(libs.bundles.retrofit) implementation(platform(libs.okhttp.bom)) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b17a6d00..90cbb635 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -58,8 +58,6 @@ android:resource="@xml/file_paths" /> - - diff --git a/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt b/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt index d26d74f8..51832982 100644 --- a/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt +++ b/app/src/main/java/com/threegap/bitnagil/BitnagilApplication.kt @@ -9,9 +9,8 @@ import coil3.memory.MemoryCache import coil3.network.okhttp.OkHttpNetworkFetcherFactory import coil3.request.crossfade import coil3.util.DebugLogger -import com.facebook.FacebookSdk -import com.facebook.LoggingBehavior import com.kakao.sdk.common.KakaoSdk +import com.threegap.bitnagil.analytics.meta.MetaAnalyticsInitializer import com.threegap.bitnagil.di.core.CoilEntryPoint import dagger.hilt.EntryPoints import dagger.hilt.android.HiltAndroidApp @@ -54,9 +53,6 @@ class BitnagilApplication : Application(), SingletonImageLoader.Factory { } private fun configureMetaAppEvents() { - if (BuildConfig.DEBUG) { - FacebookSdk.setIsDebugEnabled(true) - FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS) - } + MetaAnalyticsInitializer.initialize(isDebugBuild = BuildConfig.DEBUG) } } diff --git a/app/src/main/java/com/threegap/bitnagil/di/core/AnalyticsModule.kt b/app/src/main/java/com/threegap/bitnagil/di/core/AnalyticsModule.kt new file mode 100644 index 00000000..51ee65b5 --- /dev/null +++ b/app/src/main/java/com/threegap/bitnagil/di/core/AnalyticsModule.kt @@ -0,0 +1,21 @@ +package com.threegap.bitnagil.di.core + +import android.content.Context +import com.threegap.bitnagil.analytics.AnalyticsLogger +import com.threegap.bitnagil.analytics.meta.MetaAnalyticsLogger +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 provideAnalyticsLogger(@ApplicationContext context: Context): AnalyticsLogger = + MetaAnalyticsLogger(context) +} diff --git a/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt b/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt deleted file mode 100644 index 40d45f8e..00000000 --- a/app/src/main/java/com/threegap/bitnagil/di/presentation/AnalyticsLoggerModule.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.threegap.bitnagil.di.presentation - -import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger -import com.threegap.bitnagil.util.analytics.MetaAnalyticsLogger -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -abstract class AnalyticsLoggerModule { - @Binds - @Singleton - abstract fun bindAnalyticsLogger(impl: MetaAnalyticsLogger): AnalyticsLogger -} diff --git a/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt b/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt deleted file mode 100644 index 22f01950..00000000 --- a/app/src/main/java/com/threegap/bitnagil/util/analytics/MetaAnalyticsLogger.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.threegap.bitnagil.util.analytics - -import android.content.Context -import com.facebook.appevents.AppEventsConstants -import com.facebook.appevents.AppEventsLogger -import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class MetaAnalyticsLogger @Inject constructor( - @ApplicationContext private val context: Context, -) : AnalyticsLogger { - - private val logger by lazy { AppEventsLogger.newLogger(context) } - - override fun logOnBoardingCompleted() { - logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_REGISTRATION) - } -} diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts new file mode 100644 index 00000000..eb0652ff --- /dev/null +++ b/core/analytics/build.gradle.kts @@ -0,0 +1,43 @@ +import com.threegap.bitnagil.convention.extension.requireProperty + +plugins { + alias(libs.plugins.bitnagil.android.library) +} + +android { + buildTypes { + debug { + resValue( + type = "string", + name = "facebook_app_id", + value = requireProperty("facebook.debug.app.id", "FACEBOOK_DEBUG_APP_ID"), + ) + resValue( + type = "string", + name = "facebook_client_token", + value = requireProperty("facebook.debug.client.token", "FACEBOOK_DEBUG_CLIENT_TOKEN"), + ) + } + + release { + resValue( + type = "string", + name = "facebook_app_id", + value = requireProperty("facebook.app.id", "FACEBOOK_APP_ID"), + ) + resValue( + type = "string", + name = "facebook_client_token", + value = requireProperty("facebook.client.token", "FACEBOOK_CLIENT_TOKEN"), + ) + } + } + + buildFeatures { + resValues = true + } +} + +dependencies { + implementation(libs.facebook.core) +} diff --git a/core/analytics/src/main/AndroidManifest.xml b/core/analytics/src/main/AndroidManifest.xml new file mode 100644 index 00000000..c9d4027c --- /dev/null +++ b/core/analytics/src/main/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt similarity index 76% rename from presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt rename to core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt index 13e636c0..1ba4fe1b 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/util/analytics/AnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt @@ -1,4 +1,4 @@ -package com.threegap.bitnagil.presentation.util.analytics +package com.threegap.bitnagil.analytics interface AnalyticsLogger { /** diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsInitializer.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsInitializer.kt new file mode 100644 index 00000000..bcbcceaf --- /dev/null +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsInitializer.kt @@ -0,0 +1,13 @@ +package com.threegap.bitnagil.analytics.meta + +import com.facebook.FacebookSdk +import com.facebook.LoggingBehavior + +object MetaAnalyticsInitializer { + fun initialize(isDebugBuild: Boolean) { + if (!isDebugBuild) return + + FacebookSdk.setIsDebugEnabled(true) + FacebookSdk.addLoggingBehavior(LoggingBehavior.APP_EVENTS) + } +} diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt new file mode 100644 index 00000000..63ff4a70 --- /dev/null +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt @@ -0,0 +1,15 @@ +package com.threegap.bitnagil.analytics.meta + +import android.content.Context +import com.facebook.appevents.AppEventsConstants +import com.facebook.appevents.AppEventsLogger +import com.threegap.bitnagil.analytics.AnalyticsLogger + +class MetaAnalyticsLogger(private val context: Context) : AnalyticsLogger { + + private val logger by lazy { AppEventsLogger.newLogger(context) } + + override fun logOnBoardingCompleted() { + logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_REGISTRATION) + } +} diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index d7332e25..8a2ac92f 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -7,6 +7,7 @@ plugins { } dependencies { + implementation(projects.core.analytics) implementation(projects.core.designsystem) implementation(projects.domain) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt index a73fb9df..6fa585e2 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt @@ -3,6 +3,7 @@ package com.threegap.bitnagil.presentation.screen.onboarding import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.threegap.bitnagil.analytics.AnalyticsLogger import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingAbstractUseCase import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingsUseCase import com.threegap.bitnagil.domain.onboarding.usecase.GetRecommendOnBoardingRoutinesUseCase @@ -16,7 +17,6 @@ import com.threegap.bitnagil.presentation.screen.onboarding.model.OnBoardingPage import com.threegap.bitnagil.presentation.screen.onboarding.model.OnBoardingSetType import com.threegap.bitnagil.presentation.screen.onboarding.model.navarg.OnBoardingScreenArg import com.threegap.bitnagil.presentation.screen.onboarding.model.toUiModel -import com.threegap.bitnagil.presentation.util.analytics.AnalyticsLogger import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject diff --git a/settings.gradle.kts b/settings.gradle.kts index 99b07efd..b7b43cb3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,6 +26,7 @@ dependencyResolutionManagement { rootProject.name = "Bitnagil" include(":app") +include(":core:analytics") include(":core:datastore") include(":core:designsystem") include(":core:network") From f557ad68b0ababd02ffa988cc37c1ea2272c252b Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 17:32:21 +0900 Subject: [PATCH 06/12] =?UTF-8?q?Feat:=20=EC=98=A8=EB=B3=B4=EB=94=A9=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=EB=A5=BC=20Meta=20=ED=8A=9C=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=EC=96=BC=20=EC=99=84=EB=A3=8C=20=ED=91=9C=EC=A4=80=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=EB=A1=9C=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt index 63ff4a70..b6614301 100644 --- a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt @@ -10,6 +10,6 @@ class MetaAnalyticsLogger(private val context: Context) : AnalyticsLogger { private val logger by lazy { AppEventsLogger.newLogger(context) } override fun logOnBoardingCompleted() { - logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_REGISTRATION) + logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_TUTORIAL) } } From fa46d65ca22ce02902c9fe6039e885996e1a7b32 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 17:44:32 +0900 Subject: [PATCH 07/12] =?UTF-8?q?Fix:=20CI=20=EC=9B=8C=ED=81=AC=ED=94=8C?= =?UTF-8?q?=EB=A1=9C=EC=97=90=20Meta=20=EC=9E=90=EA=B2=A9=EC=A6=9D?= =?UTF-8?q?=EB=AA=85=20=EC=A0=84=EB=8B=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/develop_branch.yml | 8 ++++++++ .../bitnagil/convention/extension/LocalProperties.kt | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/develop_branch.yml b/.github/workflows/develop_branch.yml index f2dd0631..a1fce379 100644 --- a/.github/workflows/develop_branch.yml +++ b/.github/workflows/develop_branch.yml @@ -24,6 +24,10 @@ jobs: RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }} RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }} RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }} + FACEBOOK_APP_ID: ${{ secrets.FACEBOOK_APP_ID }} + FACEBOOK_CLIENT_TOKEN: ${{ secrets.FACEBOOK_CLIENT_TOKEN }} + FACEBOOK_DEBUG_APP_ID: ${{ secrets.FACEBOOK_DEBUG_APP_ID }} + FACEBOOK_DEBUG_CLIENT_TOKEN: ${{ secrets.FACEBOOK_DEBUG_CLIENT_TOKEN }} run: | # Keystore Decoding echo $RELEASE_STORE_BASE_64 > encoded_keystore.txt @@ -38,6 +42,10 @@ jobs: echo "release.key.password=$RELEASE_KEY_PASSWORD" >> local.properties echo "release.keystore.password=$RELEASE_STORE_PASSWORD" >> local.properties echo "release.keystore.path=../release.jks" >> local.properties + echo "facebook.app.id=$FACEBOOK_APP_ID" >> local.properties + echo "facebook.client.token=$FACEBOOK_CLIENT_TOKEN" >> local.properties + echo "facebook.debug.app.id=$FACEBOOK_DEBUG_APP_ID" >> local.properties + echo "facebook.debug.client.token=$FACEBOOK_DEBUG_CLIENT_TOKEN" >> local.properties - name: set up JDK 17 uses: actions/setup-java@v4 diff --git a/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt b/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt index 4e601beb..761d5b7c 100644 --- a/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt +++ b/build-logic/convention/src/main/java/com/threegap/bitnagil/convention/extension/LocalProperties.kt @@ -35,8 +35,8 @@ fun Project.propertyOrNull(propertyKey: String, environmentKey: String): String? localProperties.getProperty(propertyKey) ?: System.getenv(environmentKey) /** - * [propertyOrNull] 과 동일하되, 값이 없으면 설정 단계에서 실패한다. + * [propertyOrNull] 과 동일하되, 값이 없거나 비어 있으면 설정 단계에서 실패한다. */ fun Project.requireProperty(propertyKey: String, environmentKey: String): String = - propertyOrNull(propertyKey, environmentKey) + propertyOrNull(propertyKey, environmentKey)?.takeIf(String::isNotBlank) ?: throw GradleException("$propertyKey 값이 없습니다.") From c3d5fdbb52c173697422fedef945423f5a221570 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 18:48:44 +0900 Subject: [PATCH 08/12] =?UTF-8?q?Feat:=20=EC=B5=9C=EC=B4=88=20=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=20=EB=8B=AC=EC=84=B1=20=EC=97=AC=EB=B6=80=EB=A5=BC=20?= =?UTF-8?q?=EA=B8=B0=EB=A1=9D=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=BB=AC=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=EC=86=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 서버에 누적 완료 횟수나 최초 달성 여부가 없어 로컬에 플래그를 둔다. 검사와 갱신을 DataStore 의 updateData 안에서 함께 처리해, 동시 호출에도 최초 판정이 중복되지 않게 한다. --- .../bitnagil/di/core/DataStoreModule.kt | 8 +++++++ .../bitnagil/di/data/DataSourceModule.kt | 6 +++++ .../routine/model/RoutineAchievement.kt | 8 +++++++ .../RoutineAchievementSerializer.kt | 24 +++++++++++++++++++ .../storage/RoutineAchievementDataStore.kt | 5 ++++ .../RoutineAchievementDataStoreImpl.kt | 20 ++++++++++++++++ .../RoutineAchievementStorageFactory.kt | 19 +++++++++++++++ .../datasource/RoutineLocalDataSource.kt | 5 ++++ .../RoutineLocalDataSourceImpl.kt | 15 ++++++++++++ 9 files changed, 110 insertions(+) create mode 100644 core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/model/RoutineAchievement.kt create mode 100644 core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/serializer/RoutineAchievementSerializer.kt create mode 100644 core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStore.kt create mode 100644 core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStoreImpl.kt create mode 100644 core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementStorageFactory.kt create mode 100644 data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt create mode 100644 data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt diff --git a/app/src/main/java/com/threegap/bitnagil/di/core/DataStoreModule.kt b/app/src/main/java/com/threegap/bitnagil/di/core/DataStoreModule.kt index e6c1b6fb..9061abda 100644 --- a/app/src/main/java/com/threegap/bitnagil/di/core/DataStoreModule.kt +++ b/app/src/main/java/com/threegap/bitnagil/di/core/DataStoreModule.kt @@ -6,6 +6,8 @@ import com.threegap.bitnagil.datastore.auth.serializer.AuthTokenSerializer import com.threegap.bitnagil.datastore.auth.serializer.AuthTokenSerializerImpl import com.threegap.bitnagil.datastore.auth.storage.AuthTokenDataStore import com.threegap.bitnagil.datastore.auth.storage.AuthTokenStorageFactory +import com.threegap.bitnagil.datastore.routine.storage.RoutineAchievementDataStore +import com.threegap.bitnagil.datastore.routine.storage.RoutineAchievementStorageFactory import com.threegap.bitnagil.security.crypto.Crypto import dagger.Module import dagger.Provides @@ -38,4 +40,10 @@ object DataStoreModule { @ApplicationContext context: Context, serializer: AuthTokenSerializer, ): AuthTokenDataStore = AuthTokenStorageFactory.create(context, serializer) + + @Provides + @Singleton + fun provideRoutineAchievementDataStore( + @ApplicationContext context: Context, + ): RoutineAchievementDataStore = RoutineAchievementStorageFactory.create(context) } diff --git a/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt b/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt index 9e1d9fa8..941c75ad 100644 --- a/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt +++ b/app/src/main/java/com/threegap/bitnagil/di/data/DataSourceModule.kt @@ -24,7 +24,9 @@ import com.threegap.bitnagil.data.recommendroutine.datasource.RecommendRoutineDa import com.threegap.bitnagil.data.recommendroutine.datasourceImpl.RecommendRoutineDataSourceImpl import com.threegap.bitnagil.data.report.datasource.ReportDataSource import com.threegap.bitnagil.data.report.datasourceImpl.ReportDataSourceImpl +import com.threegap.bitnagil.data.routine.datasource.RoutineLocalDataSource import com.threegap.bitnagil.data.routine.datasource.RoutineRemoteDataSource +import com.threegap.bitnagil.data.routine.datasourceImpl.RoutineLocalDataSourceImpl import com.threegap.bitnagil.data.routine.datasourceImpl.RoutineRemoteDataSourceImpl import com.threegap.bitnagil.data.user.datasource.UserLocalDataSource import com.threegap.bitnagil.data.user.datasource.UserRemoteDataSource @@ -58,6 +60,10 @@ abstract class DataSourceModule { @Singleton abstract fun bindRoutineDataSource(routineDataSourceImpl: RoutineRemoteDataSourceImpl): RoutineRemoteDataSource + @Binds + @Singleton + abstract fun bindRoutineLocalDataSource(routineLocalDataSourceImpl: RoutineLocalDataSourceImpl): RoutineLocalDataSource + @Binds @Singleton abstract fun bindEmotionLocalDataSource(emotionLocalDataSourceImpl: EmotionLocalDataSourceImpl): EmotionLocalDataSource diff --git a/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/model/RoutineAchievement.kt b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/model/RoutineAchievement.kt new file mode 100644 index 00000000..7372fb8b --- /dev/null +++ b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/model/RoutineAchievement.kt @@ -0,0 +1,8 @@ +package com.threegap.bitnagil.datastore.routine.model + +import kotlinx.serialization.Serializable + +@Serializable +data class RoutineAchievement( + val firstCompletionLogged: Boolean = false, +) diff --git a/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/serializer/RoutineAchievementSerializer.kt b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/serializer/RoutineAchievementSerializer.kt new file mode 100644 index 00000000..5f619afa --- /dev/null +++ b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/serializer/RoutineAchievementSerializer.kt @@ -0,0 +1,24 @@ +package com.threegap.bitnagil.datastore.routine.serializer + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import com.threegap.bitnagil.datastore.routine.model.RoutineAchievement +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import java.io.InputStream +import java.io.OutputStream + +object RoutineAchievementSerializer : Serializer { + override val defaultValue: RoutineAchievement = RoutineAchievement() + + override suspend fun readFrom(input: InputStream): RoutineAchievement = + try { + Json.decodeFromString(input.readBytes().decodeToString()) + } catch (e: SerializationException) { + throw CorruptionException("루틴 달성 플래그 역직렬화에 실패했습니다.", e) + } + + override suspend fun writeTo(t: RoutineAchievement, output: OutputStream) { + output.write(Json.encodeToString(t).toByteArray()) + } +} diff --git a/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStore.kt b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStore.kt new file mode 100644 index 00000000..11ab3d4f --- /dev/null +++ b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStore.kt @@ -0,0 +1,5 @@ +package com.threegap.bitnagil.datastore.routine.storage + +interface RoutineAchievementDataStore { + suspend fun markFirstCompletion(): Boolean +} diff --git a/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStoreImpl.kt b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStoreImpl.kt new file mode 100644 index 00000000..41350ba7 --- /dev/null +++ b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementDataStoreImpl.kt @@ -0,0 +1,20 @@ +package com.threegap.bitnagil.datastore.routine.storage + +import androidx.datastore.core.DataStore +import com.threegap.bitnagil.datastore.routine.model.RoutineAchievement + +class RoutineAchievementDataStoreImpl( + private val dataStore: DataStore, +) : RoutineAchievementDataStore { + + override suspend fun markFirstCompletion(): Boolean { + var isFirstCompletion = false + + dataStore.updateData { currentAchievement -> + isFirstCompletion = !currentAchievement.firstCompletionLogged + currentAchievement.copy(firstCompletionLogged = true) + } + + return isFirstCompletion + } +} diff --git a/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementStorageFactory.kt b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementStorageFactory.kt new file mode 100644 index 00000000..f8c63037 --- /dev/null +++ b/core/datastore/src/main/java/com/threegap/bitnagil/datastore/routine/storage/RoutineAchievementStorageFactory.kt @@ -0,0 +1,19 @@ +package com.threegap.bitnagil.datastore.routine.storage + +import android.content.Context +import androidx.datastore.core.DataStoreFactory +import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler +import androidx.datastore.dataStoreFile +import com.threegap.bitnagil.datastore.routine.model.RoutineAchievement +import com.threegap.bitnagil.datastore.routine.serializer.RoutineAchievementSerializer + +object RoutineAchievementStorageFactory { + fun create(context: Context): RoutineAchievementDataStore { + val dataStore = DataStoreFactory.create( + serializer = RoutineAchievementSerializer, + produceFile = { context.dataStoreFile("routine-achievement.json") }, + corruptionHandler = ReplaceFileCorruptionHandler { RoutineAchievement() }, + ) + return RoutineAchievementDataStoreImpl(dataStore) + } +} diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt new file mode 100644 index 00000000..c0f9a820 --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/datasource/RoutineLocalDataSource.kt @@ -0,0 +1,5 @@ +package com.threegap.bitnagil.data.routine.datasource + +interface RoutineLocalDataSource { + suspend fun markFirstRoutineCompletion(): Result +} diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt new file mode 100644 index 00000000..95933321 --- /dev/null +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/datasourceImpl/RoutineLocalDataSourceImpl.kt @@ -0,0 +1,15 @@ +package com.threegap.bitnagil.data.routine.datasourceImpl + +import com.threegap.bitnagil.data.routine.datasource.RoutineLocalDataSource +import com.threegap.bitnagil.datastore.routine.storage.RoutineAchievementDataStore +import javax.inject.Inject + +class RoutineLocalDataSourceImpl @Inject constructor( + private val routineAchievementDataStore: RoutineAchievementDataStore, +) : RoutineLocalDataSource { + + override suspend fun markFirstRoutineCompletion(): Result = + runCatching { + routineAchievementDataStore.markFirstCompletion() + } +} From 84edfcf489e1764ad011a971d2671cc0a52ffe14 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 18:48:44 +0900 Subject: [PATCH 09/12] =?UTF-8?q?Feat:=20Meta=20=EC=B5=9C=EC=B4=88=20?= =?UTF-8?q?=EB=A3=A8=ED=8B=B4=20=EB=8B=AC=EC=84=B1=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 표준 이벤트 ACHIEVED_LEVEL 로 기록한다. 재사용 가능성이 높은 이벤트라 나중에 다른 마일스톤과 구분할 수 있도록 EVENT_PARAM_LEVEL 을 함께 보낸다. --- .../threegap/bitnagil/analytics/AnalyticsLogger.kt | 5 +++++ .../bitnagil/analytics/meta/MetaAnalyticsLogger.kt | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt index 1ba4fe1b..a65e0b89 100644 --- a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt @@ -5,4 +5,9 @@ interface AnalyticsLogger { * 온보딩 완료를 기록한다. 추천 루틴을 등록했든 건너뛰었든 동일하게 기록한다. */ fun logOnBoardingCompleted() + + /** + * 생애 최초 루틴 완료를 기록한다. 앱 설치 기준으로 1회만 호출된다. + */ + fun logFirstRoutineCompleted() } diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt index b6614301..9b9debc6 100644 --- a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt @@ -1,6 +1,7 @@ package com.threegap.bitnagil.analytics.meta import android.content.Context +import android.os.Bundle import com.facebook.appevents.AppEventsConstants import com.facebook.appevents.AppEventsLogger import com.threegap.bitnagil.analytics.AnalyticsLogger @@ -12,4 +13,16 @@ class MetaAnalyticsLogger(private val context: Context) : AnalyticsLogger { override fun logOnBoardingCompleted() { logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_TUTORIAL) } + + override fun logFirstRoutineCompleted() { + val parameters = Bundle().apply { + putString(AppEventsConstants.EVENT_PARAM_LEVEL, LEVEL_FIRST_ROUTINE_COMPLETED) + } + + logger.logEvent(AppEventsConstants.EVENT_NAME_ACHIEVED_LEVEL, parameters) + } + + private companion object { + const val LEVEL_FIRST_ROUTINE_COMPLETED = "first_routine_completed" + } } From 274702f202effea1d39ae08fded262764b39d8c2 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 18:48:44 +0900 Subject: [PATCH 10/12] =?UTF-8?q?Feat:=20=EC=B5=9C=EC=B4=88=20=EB=A3=A8?= =?UTF-8?q?=ED=8B=B4=20=EB=8B=AC=EC=84=B1=20=EC=8B=9C=20Meta=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루틴 완료 동기화가 서버에 확정된 배치에 한해, 생애 최초 1회만 이벤트를 보낸다. 완료 취소만 담긴 배치는 발화 대상에서 제외한다. 상호작용이 아니라 데이터 계층에서 확정되는 사실이므로 Repository 에서 기록한다. --- data/build.gradle.kts | 1 + .../routine/repositoryImpl/RoutineRepositoryImpl.kt | 12 ++++++++++++ .../domain/routine/model/RoutineCompletionInfos.kt | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 4cbd1917..6d8d428e 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -8,6 +8,7 @@ android { } dependencies { + implementation(projects.core.analytics) implementation(projects.core.network) implementation(projects.core.datastore) implementation(projects.domain) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt index ec7a6e9d..0fe5b513 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt @@ -1,5 +1,7 @@ package com.threegap.bitnagil.data.routine.repositoryImpl +import com.threegap.bitnagil.analytics.AnalyticsLogger +import com.threegap.bitnagil.data.routine.datasource.RoutineLocalDataSource import com.threegap.bitnagil.data.routine.datasource.RoutineRemoteDataSource import com.threegap.bitnagil.data.routine.model.request.toDto import com.threegap.bitnagil.data.routine.model.response.toDomain @@ -17,6 +19,8 @@ import javax.inject.Inject class RoutineRepositoryImpl @Inject constructor( private val routineRemoteDataSource: RoutineRemoteDataSource, + private val routineLocalDataSource: RoutineLocalDataSource, + private val analyticsLogger: AnalyticsLogger, ) : RoutineRepository { override suspend fun fetchWeeklyRoutines(startDate: String, endDate: String): Result = routineRemoteDataSource.fetchWeeklyRoutines(startDate, endDate) @@ -24,6 +28,14 @@ class RoutineRepositoryImpl @Inject constructor( override suspend fun syncRoutineCompletion(routineCompletionInfos: RoutineCompletionInfos): Result = routineRemoteDataSource.syncRoutineCompletion(routineCompletionInfos.toDto()) + .onSuccess { logFirstRoutineCompletionIfNeeded(routineCompletionInfos) } + + private suspend fun logFirstRoutineCompletionIfNeeded(routineCompletionInfos: RoutineCompletionInfos) { + if (!routineCompletionInfos.hasCompletedRoutine) return + + val isFirstCompletion = routineLocalDataSource.markFirstRoutineCompletion().getOrDefault(false) + if (isFirstCompletion) analyticsLogger.logFirstRoutineCompleted() + } override suspend fun getRoutine(routineId: String): Result = routineRemoteDataSource.getRoutine(routineId).map { it.toDomain() } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfos.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfos.kt index 3e2697a0..02c94467 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfos.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineCompletionInfos.kt @@ -2,4 +2,7 @@ package com.threegap.bitnagil.domain.routine.model data class RoutineCompletionInfos( val routineCompletionInfos: List, -) +) { + val hasCompletedRoutine: Boolean + get() = routineCompletionInfos.any { it.routineCompleteYn } +} From b6cee87b9e530118b1cff91c3849db7f6ad59678 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 21:58:49 +0900 Subject: [PATCH 11/12] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20Meta=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EA=B8=B0=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 약관 동의가 서버에 확정된 시점을 가입 완료로 보고 표준 이벤트 COMPLETED_REGISTRATION 을 기록한다. 약관 화면은 role 이 GUEST 인 신규 가입자만 도달하고 동의 성공은 한 번뿐이라, 별도 중복 방지 장치를 두지 않는다. --- .../java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt | 5 +++++ .../threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt | 4 ++++ .../bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt | 3 +++ 3 files changed, 12 insertions(+) diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt index a65e0b89..1e4ae9af 100644 --- a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/AnalyticsLogger.kt @@ -1,6 +1,11 @@ package com.threegap.bitnagil.analytics interface AnalyticsLogger { + /** + * 회원가입 완료를 기록한다. 약관 동의가 서버에 확정된 시점에 호출된다. + */ + fun logSignUpCompleted() + /** * 온보딩 완료를 기록한다. 추천 루틴을 등록했든 건너뛰었든 동일하게 기록한다. */ diff --git a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt index 9b9debc6..10d242e9 100644 --- a/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt +++ b/core/analytics/src/main/java/com/threegap/bitnagil/analytics/meta/MetaAnalyticsLogger.kt @@ -10,6 +10,10 @@ class MetaAnalyticsLogger(private val context: Context) : AnalyticsLogger { private val logger by lazy { AppEventsLogger.newLogger(context) } + override fun logSignUpCompleted() { + logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_REGISTRATION) + } + override fun logOnBoardingCompleted() { logger.logEvent(AppEventsConstants.EVENT_NAME_COMPLETED_TUTORIAL) } diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt index 0c48abd2..2eba3f33 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt @@ -1,5 +1,6 @@ package com.threegap.bitnagil.data.auth.repositoryimpl +import com.threegap.bitnagil.analytics.AnalyticsLogger import com.threegap.bitnagil.data.auth.datasource.AuthLocalDataSource import com.threegap.bitnagil.data.auth.datasource.AuthRemoteDataSource import com.threegap.bitnagil.data.auth.model.request.LoginRequest @@ -13,6 +14,7 @@ import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val authRemoteDataSource: AuthRemoteDataSource, private val authLocalDataSource: AuthLocalDataSource, + private val analyticsLogger: AnalyticsLogger, ) : AuthRepository { override suspend fun login(socialAccessToken: String, socialType: String): Result = @@ -21,6 +23,7 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun submitAgreement(termsAgreement: TermsAgreement): Result = authRemoteDataSource.submitAgreement(termsAgreement.toDto()) + .onSuccess { analyticsLogger.logSignUpCompleted() } override suspend fun logout(): Result = authRemoteDataSource.logout() From 8e4ee0fb54c8ab6d89215f60be217b6da9beeeab Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 29 Aug 2026 22:33:12 +0900 Subject: [PATCH 12/12] =?UTF-8?q?Fix:=20=EB=B6=84=EC=84=9D=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EC=98=88=EC=99=B8=EB=A5=BC=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=EC=9E=90=EB=A1=9C=EB=B6=80=ED=84=B0=20=EA=B2=A9?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Result.onSuccess 는 람다의 예외를 흡수하지 않아, 이벤트 기록이 실패하면 이미 성공한 작업이 호출자에게 실패로 전달된다. 약관 동의는 서버가 받았는데 사용자가 약관 화면에 갇히고, 온보딩 건너뛰기는 홈으로 이동이 막힌다. 세 발화 지점을 모두 runCatching 으로 감싼다. --- .../bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt | 2 +- .../data/routine/repositoryImpl/RoutineRepositoryImpl.kt | 2 +- .../presentation/screen/onboarding/OnBoardingViewModel.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt index 2eba3f33..96a89d5f 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/auth/repositoryimpl/AuthRepositoryImpl.kt @@ -23,7 +23,7 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun submitAgreement(termsAgreement: TermsAgreement): Result = authRemoteDataSource.submitAgreement(termsAgreement.toDto()) - .onSuccess { analyticsLogger.logSignUpCompleted() } + .onSuccess { runCatching { analyticsLogger.logSignUpCompleted() } } override suspend fun logout(): Result = authRemoteDataSource.logout() diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt index 0fe5b513..6f14d4ce 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt @@ -34,7 +34,7 @@ class RoutineRepositoryImpl @Inject constructor( if (!routineCompletionInfos.hasCompletedRoutine) return val isFirstCompletion = routineLocalDataSource.markFirstRoutineCompletion().getOrDefault(false) - if (isFirstCompletion) analyticsLogger.logFirstRoutineCompleted() + if (isFirstCompletion) runCatching { analyticsLogger.logFirstRoutineCompleted() } } override suspend fun getRoutine(routineId: String): Result = diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt index 6fa585e2..5a980c80 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/screen/onboarding/OnBoardingViewModel.kt @@ -346,7 +346,7 @@ class OnBoardingViewModel @AssistedInject constructor( registerRecommendOnBoardingRoutinesUseCase(selectedRecommendRoutineIds = selectedRoutineIds).fold( onSuccess = { _ -> - analyticsLogger.logOnBoardingCompleted() + runCatching { analyticsLogger.logOnBoardingCompleted() } postSideEffect(sideEffect = OnBoardingSideEffect.NavigateToHomeScreen) }, onFailure = { @@ -355,7 +355,7 @@ class OnBoardingViewModel @AssistedInject constructor( } fun skipRegisterRecommendRoutines() = intent { - analyticsLogger.logOnBoardingCompleted() + runCatching { analyticsLogger.logOnBoardingCompleted() } postSideEffect(sideEffect = OnBoardingSideEffect.NavigateToHomeScreen) } }