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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ local.properties
# ์„œ๋ช… ํ‚ค(keystore) โ€” ๋ณด์•ˆ์ƒ ์ ˆ๋Œ€ ์ปค๋ฐ‹ ๊ธˆ์ง€
*.jks
*.keystore

# Firebase (google-services.json์— API ํ‚ค ํฌํ•จ โ€” ๋กœ์ปฌ/CI์— ๊ฐ์ž ๋ฐฐ์น˜)
google-services.json
6 changes: 5 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlin.parcelize)
// TODO alias(libs.plugins.google.services)
alias(libs.plugins.google.services)
}

val properties = Properties().apply {
Expand Down Expand Up @@ -166,4 +166,8 @@ dependencies {

// Naver Login
implementation(libs.naver.login)

// Firebase (Analytics)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics)
}
46 changes: 46 additions & 0 deletions app/src/main/java/org/app/core/analytics/AnalyticsHelper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.app.core.analytics

/**
* ์•ฑ ์ „์—ญ ์ด๋ฒคํŠธ ๋กœ๊น… ์ถ”์ƒํ™”. ๊ตฌํ˜„์ฒด๋Š” Firebase Analytics([FirebaseAnalyticsHelper]).
* ViewModel ๋“ฑ์—์„œ ์ด ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•œ๋‹ค (Firebase ์ง์ ‘ ์˜์กด X โ†’ ํ…Œ์ŠคํŠธ/๊ต์ฒด ์šฉ์ด).
*/
interface AnalyticsHelper {
fun logEvent(
name: String,
params: Map<String, Any> = emptyMap(),
)
}

/** ์ปค์Šคํ…€ ์ด๋ฒคํŠธ ์ด๋ฆ„ (์ง€ํ‘œ ์ฒด๊ณ„ ๊ธฐ์ค€). Firebase ๊ทœ์น™: ์˜๋ฌธ/์ˆซ์ž/_ , 40์ž ์ด๋‚ด. */
object AnalyticsEvent {
/** ํ•ต์‹ฌ์ง€ํ‘œ: ํŽ ์ƒ์„ธ๋ฅผ 1ํšŒ+ ์กฐํšŒ */
const val PUB_DETAIL_VIEW = "pub_detail_view"

/** Activation: ์˜จ๋ณด๋”ฉ(๋‹‰๋„ค์ž„+์‘์›ํŒ€) ์™„๋ฃŒ */
const val ONBOARDING_COMPLETE = "onboarding_complete"

/** Activation: ํ•„ํ„ฐ ์ ์šฉ */
const val FILTER_APPLY = "filter_apply"

/** Activation: ๊ฒ€์ƒ‰ ์‹คํ–‰ */
const val MAP_SEARCH = "map_search"

/** Referral/์ฐธ์—ฌ: ์ œ๋ณด ์ „์†ก */
const val REPORT_SUBMIT = "report_submit"

/** Referral/์ฐธ์—ฌ: ์ฐœ(์ฆ๊ฒจ์ฐพ๊ธฐ) ์ถ”๊ฐ€ */
const val FAVORITE_ADD = "favorite_add"

/** Engagement: ์ง€๋„ ๋งํฌ(์นด์นด์˜ค/๋„ค์ด๋ฒ„) ํด๋ฆญ โ€” ์ „ํ™˜์— ๊ฐ€์žฅ ๊ทผ์ ‘ */
const val MAP_LINK_CLICK = "map_link_click"
}

/** ์ž์ฃผ ์“ฐ๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ ํ‚ค */
object AnalyticsParam {
const val PUB_ID = "pub_id"
const val MAP_TYPE = "map_type"
const val CATEGORY = "category"
const val TEAM_COUNT = "team_count"
const val REGION_COUNT = "region_count"
const val RESULT_COUNT = "result_count"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.app.core.analytics

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

/**
* Firebase Analytics ๊ธฐ๋ฐ˜ [AnalyticsHelper] ๊ตฌํ˜„.
* ์„ค์น˜/DAU/๋ฆฌํ…์…˜/์„ธ์…˜ ๋“ฑ ํ‘œ์ค€ ์ง€ํ‘œ๋Š” SDK๊ฐ€ ์ž๋™ ์ˆ˜์ง‘ํ•˜๊ณ , ์—ฌ๊ธฐ์„  ์ปค์Šคํ…€ ์ด๋ฒคํŠธ๋งŒ ์ „์†กํ•œ๋‹ค.
*/
@Singleton
class FirebaseAnalyticsHelper
@Inject
constructor(
private val firebaseAnalytics: FirebaseAnalytics,
) : AnalyticsHelper {
override fun logEvent(
name: String,
params: Map<String, Any>,
) {
firebaseAnalytics.logEvent(name) {
params.forEach { (key, value) ->
when (value) {
is String -> param(key, value)
is Long -> param(key, value)
is Int -> param(key, value.toLong())
is Double -> param(key, value)
is Float -> param(key, value.toDouble())
is Boolean -> param(key, value.toString())
else -> param(key, value.toString())
}
}
}
}
}
29 changes: 29 additions & 0 deletions app/src/main/java/org/app/core/analytics/di/AnalyticsModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.app.core.analytics.di

import android.content.Context
import com.google.firebase.analytics.FirebaseAnalytics
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import org.app.core.analytics.AnalyticsHelper
import org.app.core.analytics.FirebaseAnalyticsHelper
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {
@Binds
@Singleton
abstract fun bindAnalyticsHelper(impl: FirebaseAnalyticsHelper): AnalyticsHelper

companion object {
@Provides
@Singleton
fun provideFirebaseAnalytics(
@ApplicationContext context: Context,
): FirebaseAnalytics = FirebaseAnalytics.getInstance(context)
}
}
27 changes: 27 additions & 0 deletions app/src/main/java/org/app/presentation/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import kotlinx.collections.immutable.toImmutableMap
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.app.core.analytics.AnalyticsEvent
import org.app.core.analytics.AnalyticsHelper
import org.app.core.analytics.AnalyticsParam
import org.app.core.common.base.BaseViewModel
import org.app.core.network.isHttpConflict
import org.app.data.mapper.toPartialDetail
Expand Down Expand Up @@ -49,6 +52,7 @@ class HomeViewModel
private val userRepository: UserRepository,
private val pubRepository: PubRepository,
private val favoriteRepository: FavoriteRepository,
private val analyticsHelper: AnalyticsHelper,
) : BaseViewModel<HomeContract.State, HomeContract.Event, HomeContract.SideEffect>(
HomeContract.State(),
) {
Expand Down Expand Up @@ -158,6 +162,13 @@ class HomeViewModel
foodCodes = event.foodCodes,
)
setState { copy(filter = newFilter, showFilterBottomSheet = false) }
analyticsHelper.logEvent(
AnalyticsEvent.FILTER_APPLY,
mapOf(
AnalyticsParam.TEAM_COUNT to event.teamIds.size,
AnalyticsParam.REGION_COUNT to event.regions.size,
),
)

// ์„ ํƒ๋œ ์ง€์—ญ๋“ค์˜ ์ขŒํ‘œ๋ฅผ ๋ชจ์•„ ๋ฐ”์šด์ฆˆ ์ด๋™ ์ฒ˜๋ฆฌ
// "์„œ์šธ ์ „์ฒด"(SEOUL_ALL)๋Š” ํŠน์ • ์ง€์ ์ด ์•„๋‹ˆ๋ผ ์ „์ฒด ์กฐํšŒ์ด๋ฏ€๋กœ ์นด๋ฉ”๋ผ ์ด๋™ ๋Œ€์ƒ์—์„œ ์ œ์™ธ
Expand All @@ -174,6 +185,10 @@ class HomeViewModel
}

is HomeContract.Event.OnKakaoMapClick -> {
analyticsHelper.logEvent(
AnalyticsEvent.MAP_LINK_CLICK,
mapOf(AnalyticsParam.MAP_TYPE to "kakao"),
)
val kakaoFallback =
"https://map.kakao.com/link/map/${event.name},${event.lat},${event.lng}"
postSideEffect(
Expand All @@ -186,6 +201,10 @@ class HomeViewModel
}

is HomeContract.Event.OnNaverMapClick -> {
analyticsHelper.logEvent(
AnalyticsEvent.MAP_LINK_CLICK,
mapOf(AnalyticsParam.MAP_TYPE to "naver"),
)
val naverUrl =
"$NAVER_MAP_SCHEME://place?lat=${event.lat}&lng=${event.lng}" +
"&name=${event.name}&appname=$NAVER_MAP_APP_NAME"
Expand Down Expand Up @@ -332,6 +351,10 @@ class HomeViewModel
selectedPubDetail = detail.copy(isFavoriteed = isFavoriteed),
)
}
analyticsHelper.logEvent(
AnalyticsEvent.PUB_DETAIL_VIEW,
mapOf(AnalyticsParam.PUB_ID to pubId),
)
}.onFailure { error ->
Timber.e("ํŽ ์ƒ์„ธ ๋กœ๋“œ ์‹คํŒจ: $error")
setState {
Expand Down Expand Up @@ -409,6 +432,10 @@ class HomeViewModel
updateSelectedDetail = updateSelectedDetail,
newFavoriteId = newId,
)
analyticsHelper.logEvent(
AnalyticsEvent.FAVORITE_ADD,
mapOf(AnalyticsParam.PUB_ID to pubId),
)
postSideEffect(HomeContract.SideEffect.ShowToast("์ฆ๊ฒจ์ฐพ๊ธฐ์— ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค."))
}.onFailure { error ->
handleFavoriteFailure(error, "์ฆ๊ฒจ์ฐพ๊ธฐ ๋“ฑ๋ก์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import org.app.core.analytics.AnalyticsEvent
import org.app.core.analytics.AnalyticsHelper
import org.app.core.analytics.AnalyticsParam
import org.app.core.common.base.BaseViewModel
import org.app.data.repository.api.PubRepository
import org.app.presentation.home.model.PubSearchResult
Expand All @@ -21,6 +24,7 @@ class HomeSearchViewModel
@Inject
constructor(
private val pubRepository: PubRepository,
private val analyticsHelper: AnalyticsHelper,
) : BaseViewModel<HomeSearchContract.State, HomeSearchContract.Event, HomeSearchContract.SideEffect>(
HomeSearchContract.State(),
) {
Expand Down Expand Up @@ -70,6 +74,10 @@ class HomeSearchViewModel
setState {
copy(isLoading = false, results = results.toImmutableList(), isEmpty = results.isEmpty())
}
analyticsHelper.logEvent(
AnalyticsEvent.MAP_SEARCH,
mapOf(AnalyticsParam.RESULT_COUNT to results.size),
)
}.onFailure { error ->
Timber.e("๊ฒ€์ƒ‰ ์‹คํŒจ: $error")
setState { copy(isLoading = false, results = persistentListOf(), isEmpty = true) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import org.app.core.analytics.AnalyticsEvent
import org.app.core.analytics.AnalyticsHelper
import org.app.core.analytics.AnalyticsParam
import org.app.core.common.base.BaseViewModel
import org.app.data.repository.api.ReportRepository
import timber.log.Timber
Expand All @@ -16,6 +19,7 @@ class ReportViewModel
constructor(
savedStateHandle: SavedStateHandle,
private val reportRepository: ReportRepository,
private val analyticsHelper: AnalyticsHelper,
) : BaseViewModel<ReportContract.State, ReportContract.Event, ReportContract.SideEffect>(
ReportContract.State(pubId = savedStateHandle.get<Long?>("pubId")),
) {
Expand Down Expand Up @@ -65,6 +69,10 @@ class ReportViewModel
imageUris = s.imageUris,
).onSuccess {
Timber.d("์ œ๋ณด ์™„๋ฃŒ: reportId=${it.reportId}")
analyticsHelper.logEvent(
AnalyticsEvent.REPORT_SUBMIT,
mapOf(AnalyticsParam.CATEGORY to s.selectedCategory.apiValue),
)
postSideEffect(ReportContract.SideEffect.ShowSuccessDialog)
}.onFailure { error ->
Timber.e(error, "์ œ๋ณด ์ „์†ก ์‹คํŒจ")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableSet
import kotlinx.coroutines.launch
import org.app.core.analytics.AnalyticsEvent
import org.app.core.analytics.AnalyticsHelper
import org.app.core.analytics.AnalyticsParam
import org.app.core.common.base.BaseViewModel
import org.app.data.repository.api.UserRepository
import javax.inject.Inject
Expand All @@ -14,6 +17,7 @@ class SignUpViewModel
@Inject
constructor(
private val userRepository: UserRepository,
private val analyticsHelper: AnalyticsHelper,
savedStateHandle: SavedStateHandle,
) : BaseViewModel<SignUpContract.State, SignUpContract.Event, SignUpContract.SideEffect>(
SignUpContract.State(nickname = savedStateHandle.get<String>("nickname") ?: ""),
Expand Down Expand Up @@ -80,6 +84,10 @@ class SignUpViewModel
.postOnboarding(nickname = nickname, teamIds = teamIds)
.onSuccess {
setState { copy(isLoading = false) }
analyticsHelper.logEvent(
AnalyticsEvent.ONBOARDING_COMPLETE,
mapOf(AnalyticsParam.TEAM_COUNT to teamIds.size),
)
postSideEffect(SignUpContract.SideEffect.NavigateToComplete)
}.onFailure { error ->
setState { copy(isLoading = false) }
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
compileSdk = "37"
minSdk = "28"
targetSdk = "36"
versionCode = "2"
versionCode = "3"
versionName = "1.0.0"


Expand Down
Loading