From b324185a37b23833d6ecc2f4a67da93409ebcfd2 Mon Sep 17 00:00:00 2001 From: unam98 Date: Fri, 3 Apr 2026 16:51:34 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20Firebase=20Analytics=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=20=EA=B3=84=EC=B8=A1=20=ED=99=95=EC=9E=A5=20?= =?UTF-8?q?(Phase=201/2/3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 코스→러닝 전환율 20% 병목 분석을 위해 Firebase 이벤트를 35개에서 64개로 확장. 12개 블랙박스 화면(RunActivity, CountDown 등)에 계측 코드 삽입. - Phase 1 (P0): 러닝 전구간, 카운트다운, 코스 그리기 완성 플로우 - Phase 2 (P1): 로그인 성공/실패, 온보딩, 검색, 업로드, 보관함, 딥링크 - Phase 3 (P2): 리워드, 프로필 수정 --- .../countdown/CountDownActivity.kt | 12 ++ .../detail/CourseDetailActivity.kt | 13 ++ .../discover/pick/DiscoverPickActivity.kt | 3 + .../discover/search/DiscoverSearchActivity.kt | 7 + .../discover/upload/DiscoverUploadActivity.kt | 7 + .../runnect/presentation/draw/DrawActivity.kt | 14 ++ .../presentation/endrun/EndRunActivity.kt | 15 ++ .../login/GiveNicknameActivity.kt | 8 + .../presentation/login/LoginActivity.kt | 20 ++ .../mypage/editname/MyPageEditNameActivity.kt | 8 + .../history/detail/MyHistoryDetailActivity.kt | 7 + .../mypage/reward/MyRewardActivity.kt | 3 + .../runnect/presentation/run/RunActivity.kt | 27 +++ .../presentation/scheme/SchemeActivity.kt | 14 ++ .../storage/StorageMyDrawFragment.kt | 10 + .../storage/StorageScrapFragment.kt | 7 + .../mydrawdetail/MyDrawDetailActivity.kt | 7 + .../runnect/util/analytics/Analytics.kt | 24 ++- .../runnect/util/analytics/EventName.kt | 201 +++++++++++++++--- 19 files changed, 378 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt index ecde61abf..c8b5d0e7a 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/countdown/CountDownActivity.kt @@ -12,6 +12,9 @@ import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.data.dto.CourseData import com.runnect.runnect.databinding.ActivityCountDownBinding import com.runnect.runnect.presentation.run.RunActivity +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.getCompatibleParcelableExtra import timber.log.Timber @@ -21,6 +24,11 @@ class CountDownActivity: BindingActivity(R.layout.acti override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Analytics.logEvent( + EventName.VIEW_COUNTDOWN, + Param.COURSE_ID to courseData?.courseId + ) + val intentToRun = Intent(this, RunActivity::class.java) val numList = arrayListOf( AppCompatResources.getDrawable(this, R.drawable.anim_num1), @@ -32,6 +40,10 @@ class CountDownActivity: BindingActivity(R.layout.acti } override fun onBackPressed() { + Analytics.logEvent( + EventName.CLICK_CANCEL_COUNTDOWN, + Param.COURSE_ID to courseData?.courseId + ) finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } diff --git a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt index cbf88c9a7..00e441a86 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/detail/CourseDetailActivity.kt @@ -39,8 +39,10 @@ import com.runnect.runnect.presentation.profile.ProfileActivity import com.runnect.runnect.presentation.scheme.SchemeActivity import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_SHARE import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_USER_PROFILE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_DETAIL import com.runnect.runnect.util.custom.dialog.CommonDialogFragment import com.runnect.runnect.util.custom.dialog.CommonDialogText @@ -187,6 +189,11 @@ class CourseDetailActivity : } private fun navigateToCountDownScreen() { + Analytics.logEvent( + EventName.CLICK_RUN_FROM_DETAIL, + Param.COURSE_ID to courseDetail.courseId, + Param.DISTANCE_M to courseDetail.distance + ) Intent( this@CourseDetailActivity, CountDownActivity::class.java @@ -545,6 +552,12 @@ class CourseDetailActivity : val response = state.data binding.tvCourseDetailScrapCount.text = response.scrapCount.toString() binding.ivCourseDetailScrap.isSelected = response.scrapTF + if (!response.scrapTF) { + Analytics.logEvent( + EventName.CLICK_UNSCRAP, + Param.COURSE_ID to publicCourseId + ) + } } is UiStateV2.Failure -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt index dbf3da193..108f47212 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/pick/DiscoverPickActivity.kt @@ -14,6 +14,8 @@ import com.runnect.runnect.presentation.discover.pick.adapter.DiscoverPickAdapte import com.runnect.runnect.presentation.discover.upload.DiscoverUploadActivity import com.runnect.runnect.presentation.search.SearchActivity import com.runnect.runnect.presentation.state.UiStateV2 +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.applyScreenEnterAnimation import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation @@ -30,6 +32,7 @@ class DiscoverPickActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_DISCOVER_PICK) initLayout() addListener() diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt index be912d41a..2cc38cce3 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/search/DiscoverSearchActivity.kt @@ -24,7 +24,9 @@ import com.runnect.runnect.presentation.discover.model.EditableDiscoverCourse import com.runnect.runnect.presentation.discover.search.adapter.DiscoverSearchAdapter import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_TRY_SEARCH_COURSE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_SEARCH import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.applyScreenEnterAnimation @@ -173,6 +175,11 @@ class DiscoverSearchActivity : dismissProgressBar() showRecyclerView() searchAdapter.submitList(state.data) + Analytics.logEvent( + EventName.ACTION_COURSE_SEARCH_EXECUTE, + Param.KEYWORD to binding.etDiscoverSearchTitle.text.toString(), + Param.RESULT_COUNT to (state.data?.size ?: 0) + ) } else -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt index 6d01f379d..fc73bbc8f 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt @@ -18,7 +18,9 @@ import com.runnect.runnect.presentation.event.ScreenRefreshEvent import com.runnect.runnect.presentation.event.ScreenRefreshEventBus import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_COURSE_UPLOAD +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.analytics.EventName.VIEW_COURSE_UPLOAD import com.runnect.runnect.util.extension.applyScreenExitAnimation import com.runnect.runnect.util.extension.getCompatibleParcelableExtra @@ -113,6 +115,11 @@ class DiscoverUploadActivity : } private fun handleReturnToDiscover() { + Analytics.logEvent( + EventName.ACTION_COURSE_UPLOAD_COMPLETE, + Param.COURSE_ID to viewModel.id, + Param.DISTANCE_M to uploadCourse?.distance + ) showToast("업로드 완료!") binding.indeterminateBar.isVisible = false diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index 807b92229..e8b9ea2d1 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -49,6 +49,7 @@ import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.DepartureSetMode import com.runnect.runnect.util.analytics.Analytics import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.dialog.RequireLoginDialogFragment import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.hideKeyboard @@ -110,6 +111,7 @@ class DrawActivity : BindingActivity(R.layout.activity_draw binding.model = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_COURSE_DRAWING) initMapView() getSearchIntent() addObserver() @@ -492,6 +494,13 @@ class DrawActivity : BindingActivity(R.layout.activity_draw UiState.Loading -> showLoadingBar() UiState.Success -> { hideLoadingBar() + Analytics.logEvent( + EventName.ACTION_COURSE_DRAWING_COMPLETE, + Param.COURSE_ID to viewModel.uploadCourseId, + Param.DISTANCE_M to viewModel.distanceSum.value, + Param.POINT_COUNT to touchList.size, + Param.DEPARTURE_NAME to viewModel.departureName + ) notifyCreateFinish() } @@ -552,6 +561,11 @@ class DrawActivity : BindingActivity(R.layout.activity_draw dialog.dismiss() } } + Analytics.logEvent( + EventName.VIEW_COURSE_COMPLETE_RESULT, + Param.COURSE_ID to viewModel.uploadCourseId, + Param.DISTANCE_M to viewModel.distanceSum.value + ) dialog.show() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt index 56eec2de3..95d63ba5b 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/endrun/EndRunActivity.kt @@ -19,8 +19,10 @@ import com.runnect.runnect.databinding.ActivityEndRunBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_BACK_RUNNING_TRACKING import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_STORE_RUNNING_TRACKING +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.toast.RunnectToast import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.round @@ -60,6 +62,15 @@ class EndRunActivity: BindingActivity(R.layout.activity_e backBtn() editTextController() getIntentValue() + + val totalTimeSec = ((runToEndRunData.timerHour ?: 0) * 3600) + ((runToEndRunData.timerMinute ?: 0) * 60) + (runToEndRunData.timerSecond ?: 0) + Analytics.logEvent( + EventName.VIEW_END_RUN, + Param.COURSE_ID to runToEndRunData.courseId, + Param.TOTAL_DISTANCE_M to runToEndRunData.totalDistance, + Param.TOTAL_TIME_SEC to totalTimeSec + ) + setTimerViewModelValue() transferMinuteForCalcPace() setPaceViewModelValue() @@ -157,6 +168,10 @@ class EndRunActivity: BindingActivity(R.layout.activity_e private fun saveRecord() { binding.btnEndRunSave.setOnClickListener { Analytics.logClickedItemEvent(EVENT_CLICK_STORE_RUNNING_TRACKING) + Analytics.logEvent( + EventName.CLICK_SAVE_RUN_RECORD, + Param.COURSE_ID to viewModel.courseId.value + ) viewModel.postRecord( RequestPostRunningHistory( courseId = viewModel.courseId.value!!, diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt index 03c4742da..af8aa6f6e 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/GiveNicknameActivity.kt @@ -11,6 +11,9 @@ import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityGiveNicknameBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.preference.AuthUtil.saveToken @@ -24,6 +27,7 @@ class GiveNicknameActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_GIVE_NICKNAME) addListener() addObserver() } @@ -70,6 +74,10 @@ class GiveNicknameActivity : } private fun handleSuccessfulSignup() { + Analytics.logEvent( + EventName.ACTION_NICKNAME_COMPLETE, + Param.NICKNAME_LENGTH to (viewModel.nickName.value?.length ?: 0) + ) saveSignTokenInfo() showToast("회원가입 되었습니다") binding.indeterminateBar.isVisible = false diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt index b72efcc02..6f818da7c 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt @@ -13,8 +13,10 @@ import com.runnect.runnect.databinding.ActivityLoginBinding import com.runnect.runnect.presentation.MainActivity import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_CLICK_VISITOR import com.runnect.runnect.util.analytics.EventName.EVENT_VIEW_SOCIAL_LOGIN +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.showSnackbar import com.runnect.runnect.util.extension.showToast import com.runnect.runnect.util.preference.AuthUtil.getAccessToken @@ -106,12 +108,24 @@ class LoginActivity : } } viewModel.errorMessage.observe(this) { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_FAIL, + Param.METHOD to method, + Param.ERROR_CODE to it + ) showSnackbar(binding.root, it) Timber.tag(ContentValues.TAG).d("로그인 통신 실패: $it") } } private fun handleSuccessfulLogin() { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_SUCCESS, + Param.METHOD to method, + Param.IS_NEW_USER to false + ) saveSignTokenInfo() moveToMain() Toast.makeText(this@LoginActivity, MESSAGE_LOGIN_SUCCESS, Toast.LENGTH_SHORT).show() @@ -119,6 +133,12 @@ class LoginActivity : } private fun handleSuccessfulSignup() { + val method = if (::socialLogin.isInitialized && socialLogin is GoogleLogin) "google" else "kakao" + Analytics.logEvent( + EventName.ACTION_LOGIN_SUCCESS, + Param.METHOD to method, + Param.IS_NEW_USER to true + ) saveSignTokenInfo() moveToGiveNickName() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt index 8259b564b..39619c38e 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/editname/MyPageEditNameActivity.kt @@ -13,6 +13,9 @@ import com.runnect.runnect.R import com.runnect.runnect.binding.BindingActivity import com.runnect.runnect.databinding.ActivityMyPageEditNameBinding import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.hideKeyboard import com.runnect.runnect.util.extension.showToast import dagger.hilt.android.AndroidEntryPoint @@ -25,6 +28,7 @@ class MyPageEditNameActivity : super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_EDIT_PROFILE) initLayout() addListener() addObserver() @@ -66,6 +70,10 @@ class MyPageEditNameActivity : UiState.Loading -> binding.indeterminateBar.isVisible = true UiState.Success -> { binding.indeterminateBar.isVisible = false + Analytics.logEvent( + EventName.ACTION_EDIT_PROFILE_COMPLETE, + Param.CHANGED_FIELDS to "nickname" + ) setResult( RESULT_OK, Intent().putExtra(EXTRA_NICK_NAME, viewModel.nickName.value) diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt index a918ea023..b0c534b51 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/history/detail/MyHistoryDetailActivity.kt @@ -18,6 +18,9 @@ import com.runnect.runnect.data.dto.HistoryInfoDTO import com.runnect.runnect.databinding.ActivityMyHistoryDetailBinding import com.runnect.runnect.presentation.mypage.history.MyHistoryActivity import com.runnect.runnect.presentation.state.UiStateV2 +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.dialog.CommonDialogFragment import com.runnect.runnect.util.custom.dialog.CommonDialogText import com.runnect.runnect.util.custom.popup.PopupItem @@ -51,6 +54,10 @@ class MyHistoryDetailActivity : val runningHistory: HistoryInfoDTO? = bundle?.getCompatibleSerializableExtra(HISTORY_BUNDLE_KEY) initRunningHistory(runningHistory) + Analytics.logEvent( + EventName.VIEW_MY_HISTORY_DETAIL, + Param.RECORD_ID to runningHistory?.id + ) enterReadMode() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt index 0ad1388f4..0efabdda4 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/mypage/reward/MyRewardActivity.kt @@ -12,6 +12,8 @@ import com.runnect.runnect.data.dto.RewardStampDTO import com.runnect.runnect.databinding.ActivityMyRewardBinding import com.runnect.runnect.presentation.mypage.reward.adapter.MyRewardAdapter import com.runnect.runnect.presentation.state.UiState +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.extension.getStampResId import com.runnect.runnect.util.extension.navigateToPreviousScreenWithAnimation @@ -44,6 +46,7 @@ class MyRewardActivity : BindingActivity(R.layout.activ super.onCreate(savedInstanceState) binding.vm = viewModel binding.lifecycleOwner = this + Analytics.logEvent(EventName.VIEW_MY_REWARD) viewModel.getStampList() initLayout() diff --git a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt index 62dc71d0b..911fdddfe 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt @@ -37,6 +37,9 @@ import com.runnect.runnect.databinding.ActivityRunBinding import com.runnect.runnect.presentation.endrun.EndRunActivity import com.runnect.runnect.presentation.run.TimerService.Companion.EXTRA_TIMER_VALUE import com.runnect.runnect.presentation.run.TimerService.Companion.TIMER_UPDATE_ACTION +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.extension.round class RunActivity : BindingActivity(R.layout.activity_run), @@ -93,6 +96,13 @@ class RunActivity : BindingActivity(R.layout.activity_run), getCurrentLocation() showRecord() backButton() + + val runCourseData: CourseData? = intent.getParcelableExtra(EXTRA_COUNTDOWN_TO_RUN) + Analytics.logEvent( + EventName.ACTION_RUN_START, + Param.COURSE_ID to runCourseData?.courseId, + Param.TARGET_DISTANCE_M to runCourseData?.distance + ) } private fun initView() { @@ -160,12 +170,22 @@ class RunActivity : BindingActivity(R.layout.activity_run), private fun backButton() { binding.imgBtnBack.setOnClickListener { + Analytics.logEvent( + EventName.ACTION_RUN_ABANDON, + Param.COURSE_ID to courseId, + Param.DISTANCE_M to distanceSum + ) finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) } } override fun onBackPressed() { + Analytics.logEvent( + EventName.ACTION_RUN_ABANDON, + Param.COURSE_ID to courseId, + Param.DISTANCE_M to distanceSum + ) stopTimer() finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) @@ -316,6 +336,13 @@ class RunActivity : BindingActivity(R.layout.activity_run), private fun showRecord() { binding.btnRunFinish.setOnClickListener { stopTimer() + val totalTimeSec = ((timerData.hour ?: 0) * 3600) + ((timerData.minute ?: 0) * 60) + (timerData.second ?: 0) + Analytics.logEvent( + EventName.ACTION_RUN_COMPLETE, + Param.COURSE_ID to courseId, + Param.TOTAL_TIME_SEC to totalTimeSec, + Param.TOTAL_DISTANCE_M to distanceSum + ) val intent = Intent(this@RunActivity, EndRunActivity::class.java).apply { putExtra( EXTRA_RUN_TO_ENDRUN, diff --git a/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt index 40270d027..5ab78006d 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/scheme/SchemeActivity.kt @@ -11,6 +11,9 @@ import com.runnect.runnect.application.PreferenceManager import com.runnect.runnect.presentation.detail.CourseDetailActivity import com.runnect.runnect.presentation.login.LoginActivity import com.runnect.runnect.presentation.storage.mydrawdetail.MyDrawDetailActivity +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink.KEY_PRIVATE_COURSE_ID import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink.KEY_PUBLIC_COURSE_ID import dagger.hilt.android.AndroidEntryPoint @@ -43,6 +46,17 @@ class SchemeActivity : AppCompatActivity() { val publicCourseId = getCourseId(link, KEY_PUBLIC_COURSE_ID) val privateCourseId = getCourseId(link, KEY_PRIVATE_COURSE_ID) + val targetScreen = when { + publicCourseId != null -> "CourseDetail" + privateCourseId != null -> "MyDrawDetail" + else -> "unknown" + } + Analytics.logEvent( + EventName.SYS_DEEPLINK_OPEN, + Param.DEEPLINK_URL to link.toString(), + Param.TARGET_SCREEN to targetScreen + ) + when { publicCourseId != null -> navigateToCourseDetail( publicCourseId diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt index ebad5f64d..3be645f19 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt @@ -25,7 +25,9 @@ import com.runnect.runnect.presentation.state.UiState import com.runnect.runnect.presentation.storage.adapter.StorageMyDrawAdapter import com.runnect.runnect.presentation.storage.mydrawdetail.MyDrawDetailActivity import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName import com.runnect.runnect.util.analytics.EventName.EVENT_MY_STORAGE_TRY_REMOVE +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.callback.ItemCount import com.runnect.runnect.util.callback.listener.OnMyDrawItemClick import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration @@ -248,6 +250,10 @@ class StorageMyDrawFragment : hideLoadingBar() showMyDrawResult() updateAdapterData() + Analytics.logEvent( + EventName.VIEW_STORAGE_MY_DRAW, + Param.COURSE_COUNT to viewModel.myDrawCourses.size + ) } UiState.Failure -> { @@ -332,6 +338,10 @@ class StorageMyDrawFragment : override fun selectItem(id: Int, title: String): Boolean { return if (!isSelectAvailable) { viewModel.saveClickedCourseId(id) + Analytics.logEvent( + EventName.CLICK_MY_DRAW_COURSE, + Param.COURSE_ID to id + ) Intent(context, MyDrawDetailActivity::class.java).apply { putExtra(EXTRA_COURSE_ID, id) resultLauncher.launch(this) diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt index ae50ca65b..579841954 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt @@ -19,6 +19,9 @@ import com.runnect.runnect.presentation.event.ScreenRefreshEventBus import com.runnect.runnect.presentation.detail.CourseDetailRootScreen import com.runnect.runnect.presentation.state.UiStateV2 import com.runnect.runnect.presentation.storage.adapter.StorageScrapAdapter +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.custom.deco.GridSpacingItemDecoration import com.runnect.runnect.util.callback.ItemCount import com.runnect.runnect.util.callback.listener.OnHeartButtonClick @@ -177,6 +180,10 @@ class StorageScrapFragment : val scrapCourses = state.data updateEmptyView(scrapCourses.isEmpty(), scrapCourses.size) storageScrapAdapter.submitList(scrapCourses) + Analytics.logEvent( + EventName.VIEW_STORAGE_SCRAP, + Param.COURSE_COUNT to scrapCourses.size + ) } is UiStateV2.Failure -> { diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt index 2d679541e..25e53e643 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/mydrawdetail/MyDrawDetailActivity.kt @@ -32,6 +32,9 @@ import com.runnect.runnect.util.custom.dialog.CommonDialogText import com.runnect.runnect.util.custom.popup.PopupItem import com.runnect.runnect.util.custom.toolbar.CommonToolbarLayout import com.runnect.runnect.util.custom.toolbar.ToolbarMenu +import com.runnect.runnect.util.analytics.Analytics +import com.runnect.runnect.util.analytics.EventName +import com.runnect.runnect.util.analytics.EventName.Param import com.runnect.runnect.util.dynamiclink.RunnectDynamicLink import com.runnect.runnect.util.extension.PermissionUtil import com.runnect.runnect.util.extension.applyScreenExitAnimation @@ -60,6 +63,10 @@ class MyDrawDetailActivity : binding.lifecycleOwner = this initCourseIdExtra() + Analytics.logEvent( + EventName.VIEW_MY_DRAW_DETAIL, + Param.COURSE_ID to courseId + ) getMyDrawDetail() addListener() addObserver() diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt index 588631812..330b4fed2 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt @@ -2,7 +2,6 @@ package com.runnect.runnect.util.analytics import android.content.Context import android.os.Bundle -import android.util.StatsLog.logEvent import com.google.android.gms.common.wrappers.InstantApps import com.google.firebase.analytics.FirebaseAnalytics @@ -16,7 +15,6 @@ object Analytics { fun initializeFirebaseAnalytics(context: Context) { firebaseAnalytics = FirebaseAnalytics.getInstance(context) - // 현재 앱이 인스턴트 앱인지를 확인하고, 그 여부에 따라 UserProperty 다르게 세팅 if (InstantApps.isInstantApp(context)) { setUserProperty(ANALYTICS_USER_PROP, STATUS_INSTANT) } else { @@ -37,4 +35,26 @@ object Analytics { firebaseAnalytics?.logEvent(eventName, bundle) } + fun logEvent(eventName: String, params: Bundle? = null) { + firebaseAnalytics?.logEvent(eventName, params) + } + + fun logEvent(eventName: String, vararg params: Pair) { + val bundle = if (params.isNotEmpty()) { + Bundle().apply { + for ((key, value) in params) { + when (value) { + is String -> putString(key, value) + is Int -> putInt(key, value) + is Long -> putLong(key, value) + is Float -> putFloat(key, value) + is Double -> putDouble(key, value) + is Boolean -> putBoolean(key, value) + null -> {} + } + } + } + } else null + firebaseAnalytics?.logEvent(eventName, bundle) + } } diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt index a8addd825..e1583b7b0 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt @@ -2,8 +2,12 @@ package com.runnect.runnect.util.analytics object EventName { + // ======================================== + // App Start / Onboarding + // ======================================== + // App - const val EVENT_VIEW_HOME = "view_home" // 앱 실행 + const val EVENT_VIEW_HOME = "view_home" const val EVENT_VIEW_SOCIAL_LOGIN = "view_social_login" // Login @@ -11,22 +15,19 @@ object EventName { const val EVENT_CLICK_KAKAO_LOGIN = "click_kakao_login" const val EVENT_CLICK_VISITOR = "click_visitor" - // Running Tracking - const val EVENT_CLICK_BACK_RUNNING_TRACKING = "click_back_running_tracking" - const val EVENT_CLICK_STORE_RUNNING_TRACKING = "click_store_running_tracking" + // Phase 2: Login result + const val ACTION_LOGIN_SUCCESS = "action_login_success" + const val ACTION_LOGIN_FAIL = "action_login_fail" - // Draw Course - const val EVENT_CLICK_COURSE_DRAWING = "click_course_drawing" - const val EVENT_CLICK_CURRENT_LOCATE = "click_current_locate" - const val EVENT_CLICK_MAP_LOCATE = "click_map_locate" - const val EVENT_CLICK_STORED_AFTER_COURSE_COMPLETE = "click_stored_after_course_complete" - const val EVENT_CLICK_RUN_AFTER_COURSE_COMPLETE = "click_run_after_course_complete" + // Phase 2: Onboarding + const val VIEW_GIVE_NICKNAME = "view_give_nickname" + const val ACTION_NICKNAME_COMPLETE = "action_nickname_complete" - // Navigation Menu - const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_nav_course_drawing" - const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_nav_course_discovery" - const val EVENT_CLICK_NAV_STORAGE = "click_nav_storage" - const val EVENT_CLICK_NAV_MY_PAGE = "click_nav_my_page" + // Phase 3: Onboarding + const val CLICK_NICKNAME_SKIP = "click_nickname_skip" + + // Phase 1: App start + const val SYS_APP_OPEN = "sys_app_open" // Visitor Mode const val EVENT_CLICK_JOIN_IN_COURSE_DRAWING = "click_join_in_course_drawing" @@ -34,7 +35,56 @@ object EventName { const val EVENT_CLICK_JOIN_IN_STORAGE = "click_join_in_storage" const val EVENT_CLICK_JOIN_IN_MY_PAGE = "click_join_in_my_page" - // Discover + // ======================================== + // Course Drawing + // ======================================== + + const val EVENT_CLICK_COURSE_DRAWING = "click_course_drawing" + const val EVENT_CLICK_CURRENT_LOCATE = "click_current_locate" + const val EVENT_CLICK_MAP_LOCATE = "click_map_locate" + const val EVENT_CLICK_STORED_AFTER_COURSE_COMPLETE = "click_stored_after_course_complete" + const val EVENT_CLICK_RUN_AFTER_COURSE_COMPLETE = "click_run_after_course_complete" + + // Phase 1: Course drawing flow + const val VIEW_COURSE_DRAWING = "view_course_drawing" + const val ACTION_COURSE_DRAWING_COMPLETE = "action_course_drawing_complete" + const val VIEW_COURSE_COMPLETE_RESULT = "view_course_complete_result" + + // Phase 2: Course drawing detail + const val ACTION_COURSE_DRAWING_START = "action_course_drawing_start" + const val CLICK_SHARE_AFTER_COURSE_COMPLETE = "click_share_after_course_complete" + + // ======================================== + // Running + // ======================================== + + // Existing + const val EVENT_CLICK_BACK_RUNNING_TRACKING = "click_back_running_tracking" + const val EVENT_CLICK_STORE_RUNNING_TRACKING = "click_store_running_tracking" + + // Phase 1: Countdown + const val VIEW_COUNTDOWN = "view_countdown" + const val CLICK_CANCEL_COUNTDOWN = "click_cancel_countdown" + + // Phase 1: Running progress + const val ACTION_RUN_START = "action_run_start" + const val ACTION_RUN_PAUSE = "action_run_pause" + const val ACTION_RUN_RESUME = "action_run_resume" + const val ACTION_RUN_COMPLETE = "action_run_complete" + const val ACTION_RUN_ABANDON = "action_run_abandon" + + // Phase 1: End run + const val VIEW_END_RUN = "view_end_run" + + // Phase 2: End run actions + const val CLICK_SAVE_RUN_RECORD = "click_save_run_record" + const val CLICK_SHARE_RUN_RECORD = "click_share_run_record" + const val CLICK_RUN_AGAIN = "click_run_again" + + // ======================================== + // Course Discovery + // ======================================== + const val EVENT_CLICK_UPLOAD_BUTTON = "click_upload_button" const val EVENT_CLICK_DATE = "click_date_sort" const val EVENT_CLICK_SCRAP = "click_scrap_sort" @@ -49,7 +99,46 @@ object EventName { const val VIEW_USER_PROFILE = "view_user_profile" const val VIEW_COURSE_UPLOAD = "view_course_upload" + // Phase 1: Course detail -> Run + const val CLICK_RUN_FROM_DETAIL = "click_run_from_detail" + + // Phase 2: Course discovery detail + const val ACTION_COURSE_SEARCH_EXECUTE = "action_course_search_execute" + const val VIEW_DISCOVER_PICK = "view_discover_pick" + const val CLICK_UNSCRAP = "click_unscrap" + const val ACTION_COURSE_UPLOAD_COMPLETE = "action_course_upload_complete" + + // ======================================== + // Storage + // ======================================== + + const val EVENT_CLICK_MY_DRAW_STORAGE_COURSE_DRAWING_START = + "click_my_storage_course_drawing_start" + const val EVENT_CLICK_SCRAP_COURSE = "click_scrap_course" + const val EVENT_MY_STORAGE_TRY_REMOVE = "click_my_storage_try_remove" + + // Phase 1: Storage -> Run + const val CLICK_RUN_FROM_STORAGE = "click_run_from_storage" + + // Phase 2: Storage views + const val VIEW_STORAGE_MY_DRAW = "view_storage_my_draw" + const val VIEW_STORAGE_SCRAP = "view_storage_scrap" + const val CLICK_MY_DRAW_COURSE = "click_my_draw_course" + const val VIEW_MY_DRAW_DETAIL = "view_my_draw_detail" + + // ======================================== + // Navigation Menu + // ======================================== + + const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_nav_course_drawing" + const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_nav_course_discovery" + const val EVENT_CLICK_NAV_STORAGE = "click_nav_storage" + const val EVENT_CLICK_NAV_MY_PAGE = "click_nav_my_page" + + // ======================================== // MyPage + // ======================================== + const val EVENT_CLICK_RUNNING_RECORD = "click_running_record" const val EVENT_CLICK_GOAL_REWARD = "click_goal_reward" const val EVENT_CLICK_UPLOADED_COURSE = "click_uploaded_course" @@ -58,21 +147,79 @@ object EventName { const val EVENT_CLICK_COURSE_DRAWING_IN_RUNNING_RECORD = "click_course_drawing_in_running_record" - // MySettingAccountInfo + // Phase 2: MyPage detail + const val VIEW_MY_HISTORY_DETAIL = "view_my_history_detail" + const val CLICK_RUN_AGAIN_FROM_HISTORY = "click_run_again_from_history" + + // Phase 3: MyPage + const val VIEW_MY_REWARD = "view_my_reward" + const val VIEW_EDIT_PROFILE = "view_edit_profile" + const val ACTION_EDIT_PROFILE_COMPLETE = "action_edit_profile_complete" + + // MyUpload + const val EVENT_CLICK_COURSE_UPLOAD_IN_UPLOADED_COURSE = + "click_course_upload_in_uploaded_course" + + // ======================================== + // Settings / System + // ======================================== + const val EVENT_VIEW_SUCCESS_LOGOUT = "view_success_logout" const val EVENT_CLICK_TRY_LOGOUT = "click_try_logout" const val EVENT_VIEW_SUCCESS_WITHDRAW = "view_success_withdraw" const val EVENT_CLICK_TRY_WITHDRAW = "click_try_withdraw" - // MyUpload - const val EVENT_CLICK_COURSE_UPLOAD_IN_UPLOADED_COURSE = - "click_course_upload_in_uploaded_course" + // Phase 2: System + const val SYS_DEEPLINK_OPEN = "sys_deeplink_open" - // StorageMain - const val EVENT_CLICK_MY_DRAW_STORAGE_COURSE_DRAWING_START = - "click_my_storage_course_drawing_start" - const val EVENT_CLICK_SCRAP_COURSE = "click_scrap_course" + // ======================================== + // Parameter Keys + // ======================================== - // StorageMyDraw - const val EVENT_MY_STORAGE_TRY_REMOVE = "click_my_storage_try_remove" -} \ No newline at end of file + object Param { + const val SOURCE = "source" + const val COURSE_ID = "course_id" + const val DISTANCE_M = "distance_m" + const val POINT_COUNT = "point_count" + const val DRAWING_TIME_SEC = "drawing_time_sec" + const val DEPARTURE_NAME = "departure_name" + const val ELAPSED_SEC = "elapsed_sec" + const val TOTAL_TIME_SEC = "total_time_sec" + const val TOTAL_DISTANCE_M = "total_distance_m" + const val AVG_PACE_SEC_PER_KM = "avg_pace_sec_per_km" + const val PAUSE_COUNT = "pause_count" + const val TOTAL_PAUSE_SEC = "total_pause_sec" + const val PAUSE_DURATION_SEC = "pause_duration_sec" + const val COMPLETION_RATE = "completion_rate" + const val ABANDON_REASON = "abandon_reason" + const val TARGET_DISTANCE_M = "target_distance_m" + const val COUNTDOWN_SEC_REMAINING = "countdown_sec_remaining" + const val PACE_SEC_PER_KM = "pace_sec_per_km" + const val METHOD = "method" + const val IS_NEW_USER = "is_new_user" + const val ERROR_CODE = "error_code" + const val NICKNAME_LENGTH = "nickname_length" + const val SHARE_TARGET = "share_target" + const val KEYWORD = "keyword" + const val RESULT_COUNT = "result_count" + const val STORAGE_TYPE = "storage_type" + const val DAYS_SINCE_CREATED = "days_since_created" + const val COURSE_COUNT = "course_count" + const val RECORD_ID = "record_id" + const val TOTAL_REWARDS = "total_rewards" + const val CHANGED_FIELDS = "changed_fields" + const val LAUNCH_TYPE = "launch_type" + const val REFERRER = "referrer" + const val DEEPLINK_URL = "deeplink_url" + const val TARGET_SCREEN = "target_screen" + const val SCREEN_NAME = "screen_name" + const val LAST_ACTION = "last_action" + const val EXCEPTION_TYPE = "exception_type" + const val STACK_TRACE_HASH = "stack_trace_hash" + const val WITHDRAW_REASON = "withdraw_reason" + const val DAYS_SINCE_SIGNUP = "days_since_signup" + const val TOTAL_RUNS = "total_runs" + const val TOTAL_COURSES = "total_courses" + const val HAS_DESCRIPTION = "has_description" + } +} From 5847567856f7c1494246762a228b0ddcc4d27b82 Mon Sep 17 00:00:00 2001 From: unam98 Date: Fri, 3 Apr 2026 17:13:01 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20=EC=BD=94=EB=93=9C=EB=A6=AC=EB=B7=B0?= =?UTF-8?q?=20=EB=B0=98=EC=98=81=20-=20=EA=B1=B0=EB=A6=AC=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=EB=B3=80=ED=99=98,=20Firebase=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20=ED=83=80=EC=9E=85,=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EA=B7=9C=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DISTANCE_M 파라미터 km→m 변환 (DrawActivity, RunActivity) - Analytics.logEvent에서 Int→Long, Float→Double, Boolean→Long 변환 - LoginActivity ERROR_CODE를 안정적 상수로 변경 - DiscoverUploadActivity distance를 Double로 파싱 - EventName.kt 과도한 구분선 주석 제거 --- .../discover/upload/DiscoverUploadActivity.kt | 2 +- .../runnect/presentation/draw/DrawActivity.kt | 7 +++++-- .../presentation/login/LoginActivity.kt | 2 +- .../runnect/presentation/run/RunActivity.kt | 10 ++++++---- .../runnect/util/analytics/Analytics.kt | 6 +++--- .../runnect/util/analytics/EventName.kt | 18 ------------------ 6 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt index fc73bbc8f..2710b9c0a 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/discover/upload/DiscoverUploadActivity.kt @@ -118,7 +118,7 @@ class DiscoverUploadActivity : Analytics.logEvent( EventName.ACTION_COURSE_UPLOAD_COMPLETE, Param.COURSE_ID to viewModel.id, - Param.DISTANCE_M to uploadCourse?.distance + Param.DISTANCE_M to uploadCourse?.distance?.toDoubleOrNull() ) showToast("업로드 완료!") binding.indeterminateBar.isVisible = false diff --git a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt index e8b9ea2d1..f75df8824 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/draw/DrawActivity.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.presentation.draw +import kotlin.math.roundToInt import android.content.Intent import android.graphics.Bitmap import android.graphics.Color @@ -494,10 +495,11 @@ class DrawActivity : BindingActivity(R.layout.activity_draw UiState.Loading -> showLoadingBar() UiState.Success -> { hideLoadingBar() + val distanceM = ((viewModel.distanceSum.value ?: 0f) * 1000f).roundToInt() Analytics.logEvent( EventName.ACTION_COURSE_DRAWING_COMPLETE, Param.COURSE_ID to viewModel.uploadCourseId, - Param.DISTANCE_M to viewModel.distanceSum.value, + Param.DISTANCE_M to distanceM, Param.POINT_COUNT to touchList.size, Param.DEPARTURE_NAME to viewModel.departureName ) @@ -561,10 +563,11 @@ class DrawActivity : BindingActivity(R.layout.activity_draw dialog.dismiss() } } + val resultDistanceM = ((viewModel.distanceSum.value ?: 0f) * 1000f).roundToInt() Analytics.logEvent( EventName.VIEW_COURSE_COMPLETE_RESULT, Param.COURSE_ID to viewModel.uploadCourseId, - Param.DISTANCE_M to viewModel.distanceSum.value + Param.DISTANCE_M to resultDistanceM ) dialog.show() } diff --git a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt index 6f818da7c..9c9a54b42 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/login/LoginActivity.kt @@ -112,7 +112,7 @@ class LoginActivity : Analytics.logEvent( EventName.ACTION_LOGIN_FAIL, Param.METHOD to method, - Param.ERROR_CODE to it + Param.ERROR_CODE to "LOGIN_FAIL" ) showSnackbar(binding.root, it) Timber.tag(ContentValues.TAG).d("로그인 통신 실패: $it") diff --git a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt index 911fdddfe..fe9e4257b 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/run/RunActivity.kt @@ -1,5 +1,6 @@ package com.runnect.runnect.presentation.run +import kotlin.math.roundToInt import android.content.BroadcastReceiver import android.content.ComponentName import android.content.Context @@ -98,10 +99,11 @@ class RunActivity : BindingActivity(R.layout.activity_run), backButton() val runCourseData: CourseData? = intent.getParcelableExtra(EXTRA_COUNTDOWN_TO_RUN) + val targetDistanceM = runCourseData?.distance?.let { (it * 1000f).roundToInt() } Analytics.logEvent( EventName.ACTION_RUN_START, Param.COURSE_ID to runCourseData?.courseId, - Param.TARGET_DISTANCE_M to runCourseData?.distance + Param.TARGET_DISTANCE_M to targetDistanceM ) } @@ -173,7 +175,7 @@ class RunActivity : BindingActivity(R.layout.activity_run), Analytics.logEvent( EventName.ACTION_RUN_ABANDON, Param.COURSE_ID to courseId, - Param.DISTANCE_M to distanceSum + Param.DISTANCE_M to (distanceSum * 1000.0).roundToInt() ) finish() overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right) @@ -184,7 +186,7 @@ class RunActivity : BindingActivity(R.layout.activity_run), Analytics.logEvent( EventName.ACTION_RUN_ABANDON, Param.COURSE_ID to courseId, - Param.DISTANCE_M to distanceSum + Param.DISTANCE_M to (distanceSum * 1000.0).roundToInt() ) stopTimer() finish() @@ -341,7 +343,7 @@ class RunActivity : BindingActivity(R.layout.activity_run), EventName.ACTION_RUN_COMPLETE, Param.COURSE_ID to courseId, Param.TOTAL_TIME_SEC to totalTimeSec, - Param.TOTAL_DISTANCE_M to distanceSum + Param.TOTAL_DISTANCE_M to (distanceSum * 1000.0).roundToInt() ) val intent = Intent(this@RunActivity, EndRunActivity::class.java).apply { putExtra( diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt index 330b4fed2..b974465d6 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/Analytics.kt @@ -45,11 +45,11 @@ object Analytics { for ((key, value) in params) { when (value) { is String -> putString(key, value) - is Int -> putInt(key, value) + is Int -> putLong(key, value.toLong()) is Long -> putLong(key, value) - is Float -> putFloat(key, value) + is Float -> putDouble(key, value.toDouble()) is Double -> putDouble(key, value) - is Boolean -> putBoolean(key, value) + is Boolean -> putLong(key, if (value) 1L else 0L) null -> {} } } diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt index e1583b7b0..0e47b195c 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt @@ -2,9 +2,7 @@ package com.runnect.runnect.util.analytics object EventName { - // ======================================== // App Start / Onboarding - // ======================================== // App const val EVENT_VIEW_HOME = "view_home" @@ -35,9 +33,7 @@ object EventName { const val EVENT_CLICK_JOIN_IN_STORAGE = "click_join_in_storage" const val EVENT_CLICK_JOIN_IN_MY_PAGE = "click_join_in_my_page" - // ======================================== // Course Drawing - // ======================================== const val EVENT_CLICK_COURSE_DRAWING = "click_course_drawing" const val EVENT_CLICK_CURRENT_LOCATE = "click_current_locate" @@ -54,9 +50,7 @@ object EventName { const val ACTION_COURSE_DRAWING_START = "action_course_drawing_start" const val CLICK_SHARE_AFTER_COURSE_COMPLETE = "click_share_after_course_complete" - // ======================================== // Running - // ======================================== // Existing const val EVENT_CLICK_BACK_RUNNING_TRACKING = "click_back_running_tracking" @@ -81,9 +75,7 @@ object EventName { const val CLICK_SHARE_RUN_RECORD = "click_share_run_record" const val CLICK_RUN_AGAIN = "click_run_again" - // ======================================== // Course Discovery - // ======================================== const val EVENT_CLICK_UPLOAD_BUTTON = "click_upload_button" const val EVENT_CLICK_DATE = "click_date_sort" @@ -108,9 +100,7 @@ object EventName { const val CLICK_UNSCRAP = "click_unscrap" const val ACTION_COURSE_UPLOAD_COMPLETE = "action_course_upload_complete" - // ======================================== // Storage - // ======================================== const val EVENT_CLICK_MY_DRAW_STORAGE_COURSE_DRAWING_START = "click_my_storage_course_drawing_start" @@ -126,18 +116,14 @@ object EventName { const val CLICK_MY_DRAW_COURSE = "click_my_draw_course" const val VIEW_MY_DRAW_DETAIL = "view_my_draw_detail" - // ======================================== // Navigation Menu - // ======================================== const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_nav_course_drawing" const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_nav_course_discovery" const val EVENT_CLICK_NAV_STORAGE = "click_nav_storage" const val EVENT_CLICK_NAV_MY_PAGE = "click_nav_my_page" - // ======================================== // MyPage - // ======================================== const val EVENT_CLICK_RUNNING_RECORD = "click_running_record" const val EVENT_CLICK_GOAL_REWARD = "click_goal_reward" @@ -160,9 +146,7 @@ object EventName { const val EVENT_CLICK_COURSE_UPLOAD_IN_UPLOADED_COURSE = "click_course_upload_in_uploaded_course" - // ======================================== // Settings / System - // ======================================== const val EVENT_VIEW_SUCCESS_LOGOUT = "view_success_logout" const val EVENT_CLICK_TRY_LOGOUT = "click_try_logout" @@ -172,9 +156,7 @@ object EventName { // Phase 2: System const val SYS_DEEPLINK_OPEN = "sys_deeplink_open" - // ======================================== // Parameter Keys - // ======================================== object Param { const val SOURCE = "source" From ca93d92d5fc34771b3ded78ab02ef66d4fc74997 Mon Sep 17 00:00:00 2001 From: unam98 Date: Fri, 3 Apr 2026 17:29:40 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=B3=B4=EA=B4=80=ED=95=A8=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EC=A7=84=EC=9E=85=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=88=84=EB=9D=BD=20=EB=B3=B4=EC=B6=A9=20(iOS=20?= =?UTF-8?q?=EC=8B=B1=ED=81=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - StorageMyDrawFragment: VIEW_STORAGE_MY_DRAW 추가 - StorageScrapFragment: VIEW_STORAGE_SCRAP 추가 --- .../runnect/presentation/storage/StorageMyDrawFragment.kt | 1 + .../runnect/runnect/presentation/storage/StorageScrapFragment.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt index 3be645f19..dfae10425 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageMyDrawFragment.kt @@ -71,6 +71,7 @@ class StorageMyDrawFragment : super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner + Analytics.logEvent(EventName.VIEW_STORAGE_MY_DRAW) initLayout() initAdapter() getCourse() diff --git a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt index 579841954..a45e0b8a3 100644 --- a/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt +++ b/app/src/main/java/com/runnect/runnect/presentation/storage/StorageScrapFragment.kt @@ -48,6 +48,7 @@ class StorageScrapFragment : super.onViewCreated(view, savedInstanceState) binding.lifecycleOwner = viewLifecycleOwner + Analytics.logEvent(EventName.VIEW_STORAGE_SCRAP) getMyScrapCourses() initLayout() initAdapter() From 6d2aebee2826f80c35cb32e3c265e63a6325bfac Mon Sep 17 00:00:00 2001 From: unam98 Date: Fri, 3 Apr 2026 17:31:50 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=ED=83=AD=EB=B0=94=20=EC=9D=B4?= =?UTF-8?q?=EB=B2=A4=ED=8A=B8=EB=AA=85=20iOS=20=EA=B8=B0=EC=A4=80=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - click_nav_course_drawing → click_course_drawing_tab_bar - click_nav_course_discovery → click_course_discovery_tab_bar - click_nav_storage → click_storage_tab_bar - click_nav_my_page → click_my_page_tab_bar --- .../java/com/runnect/runnect/util/analytics/EventName.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt index 0e47b195c..dd79c4e6c 100644 --- a/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt +++ b/app/src/main/java/com/runnect/runnect/util/analytics/EventName.kt @@ -118,10 +118,10 @@ object EventName { // Navigation Menu - const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_nav_course_drawing" - const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_nav_course_discovery" - const val EVENT_CLICK_NAV_STORAGE = "click_nav_storage" - const val EVENT_CLICK_NAV_MY_PAGE = "click_nav_my_page" + const val EVENT_CLICK_NAV_COURSE_DRAWING = "click_course_drawing_tab_bar" + const val EVENT_CLICK_NAV_COURSE_DISCOVERY = "click_course_discovery_tab_bar" + const val EVENT_CLICK_NAV_STORAGE = "click_storage_tab_bar" + const val EVENT_CLICK_NAV_MY_PAGE = "click_my_page_tab_bar" // MyPage