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
22 changes: 0 additions & 22 deletions app/src/main/java/com/eatssu/android/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.google.firebase.ktx.Firebase
import com.kakao.sdk.common.KakaoSdk
import com.posthog.android.PostHogAndroid
import com.posthog.android.PostHogAndroidConfig
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -36,26 +34,6 @@ class App : Application(), Configuration.Provider {
Firebase.analytics.setAnalyticsCollectionEnabled(true)
}

setupPostHog()
}

private fun setupPostHog() {
// Create a PostHog Config with the given API key and host
val config = PostHogAndroidConfig(
apiKey = BuildConfig.POSTHOG_API_KEY,
host = BuildConfig.POSTHOG_HOST,
).apply {
sessionReplay = true
sessionReplayConfig.screenshot = true
if (BuildConfig.DEBUG) {
sessionReplayConfig.maskAllTextInputs = false
sessionReplayConfig.maskAllImages = false
}
}


// Setup PostHog with the given Context and Config
PostHogAndroid.setup(this, config)
}

override val workManagerConfiguration: Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.eatssu.android.analytics

import com.eatssu.android.domain.model.College
import com.eatssu.android.domain.model.Department
import com.eatssu.common.analytics.AnalyticsIdentity
import com.eatssu.common.analytics.AnalyticsTracker
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class AnalyticsIdentityManager @Inject constructor(
private val analyticsTracker: AnalyticsTracker,
) {

fun identifyUser(
email: String,
nickname: String? = null,
college: College? = null,
department: Department? = null,
) {
val trimmedEmail = email.trim()
if (trimmedEmail.isBlank()) return
val selectedCollege =
college?.takeUnless { it.collegeId == -1 || it.collegeName.isBlank() || it.collegeName == "단과대" }
val selectedDepartment =
department?.takeUnless {
it.departmentId == -1 || it.departmentName.isBlank() || it.departmentName == "학과"
}

analyticsTracker.identify(
AnalyticsIdentity(
distinctId = trimmedEmail,
email = trimmedEmail,
nickname = nickname?.trim()?.takeIf(String::isNotBlank),
collegeId = selectedCollege?.collegeId,
collegeName = selectedCollege?.collegeName,
departmentId = selectedDepartment?.departmentId,
departmentName = selectedDepartment?.departmentName,
),
)
}

fun resetIdentity() {
analyticsTracker.resetIdentity()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.eatssu.android.analytics

import com.eatssu.common.analytics.AnalyticsIdentity

internal fun AnalyticsIdentity.toProperties(): Map<String, Any> =
buildMap {
put("email", email)
nickname?.let { put("nickname", it) }
collegeId?.let { put("college_id", it) }
collegeName?.let { put("college_name", it) }
departmentId?.let { put("department_id", it) }
departmentName?.let { put("department_name", it) }
}
31 changes: 31 additions & 0 deletions app/src/main/java/com/eatssu/android/analytics/ComposeAnalytics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.eatssu.android.analytics

import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import com.eatssu.common.analytics.AnalyticsEvent
import com.eatssu.common.analytics.AnalyticsIdentity
import com.eatssu.common.analytics.AnalyticsTracker

val LocalAnalyticsTracker = staticCompositionLocalOf<AnalyticsTracker> { NoOpAnalyticsTracker }

@Composable
fun ProvideAnalyticsTracker(
analyticsTracker: AnalyticsTracker,
content: @Composable () -> Unit,
) {
CompositionLocalProvider(
LocalAnalyticsTracker provides analyticsTracker,
content = content,
)
}

private object NoOpAnalyticsTracker : AnalyticsTracker {
override val id: String = "noop"

override fun track(event: AnalyticsEvent) = Unit

override fun identify(identity: AnalyticsIdentity) = Unit

override fun resetIdentity() = Unit
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.eatssu.android.analytics

import com.eatssu.common.analytics.AnalyticsEvent
import com.eatssu.common.analytics.AnalyticsIdentity
import com.eatssu.common.analytics.AnalyticsTracker
import javax.inject.Inject
import javax.inject.Singleton
import timber.log.Timber
import kotlin.jvm.JvmSuppressWildcards

@Singleton
class DefaultAnalyticsTracker @Inject constructor(
trackers: Set<@JvmSuppressWildcards AnalyticsTracker>,
) : AnalyticsTracker {

override val id: String = "default"

private val trackers: List<AnalyticsTracker> = trackers.distinctBy(AnalyticsTracker::id)

override fun track(event: AnalyticsEvent) {
trackers.forEach { tracker ->
runCatching {
tracker.track(event)
}.onFailure { throwable ->
Timber.e(
throwable,
"Failed to track analytics event %s via %s",
event.eventName,
tracker.id,
)
}
}
}

override fun identify(identity: AnalyticsIdentity) {
if (identity.distinctId.isBlank()) return

trackers.forEach { tracker ->
runCatching {
tracker.identify(identity)
}.onFailure { throwable ->
Timber.e(
throwable,
"Failed to identify analytics user via %s",
tracker.id,
)
}
}
}

override fun resetIdentity() {
trackers.forEach { tracker ->
runCatching {
tracker.resetIdentity()
}.onFailure { throwable ->
Timber.e(
throwable,
"Failed to reset analytics identity via %s",
tracker.id,
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.eatssu.android.analytics

import android.os.Bundle
import com.eatssu.common.analytics.AnalyticsEvent
import com.eatssu.common.analytics.AnalyticsIdentity
import com.eatssu.common.analytics.AnalyticsTracker
import com.google.firebase.analytics.FirebaseAnalytics
import javax.inject.Inject

class FirebaseAnalyticsTracker @Inject constructor(
private val firebaseAnalytics: FirebaseAnalytics,
) : AnalyticsTracker {
override val id: String = "firebase"

override fun track(event: AnalyticsEvent) {
val payload = event.toPayload()
firebaseAnalytics.logEvent(payload.eventName, payload.properties.toBundle())
}

override fun identify(identity: AnalyticsIdentity) {
firebaseAnalytics.setUserId(identity.distinctId)
identity.toProperties().forEach { (key, value) ->
firebaseAnalytics.setUserProperty(key, value.toString())
}
}

override fun resetIdentity() {
firebaseAnalytics.setUserId(null)
}
}

internal fun Map<String, Any>.toBundle(): Bundle =
Bundle().apply {
forEach { (key, value) ->
when (value) {
is String -> putString(key, value)
is Int -> putInt(key, value)
is Long -> putLong(key, value)
is Double -> putDouble(key, value)
is Float -> putFloat(key, value)
is Boolean -> putBoolean(key, value)
is Bundle -> putBundle(key, value)
else -> putString(key, value.toString())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.eatssu.android.analytics

import com.eatssu.common.analytics.AnalyticsEvent
import com.eatssu.common.analytics.AnalyticsIdentity
import com.eatssu.common.analytics.AnalyticsTracker
import com.posthog.PostHogInterface
import javax.inject.Inject

class PostHogAnalyticsTracker @Inject constructor(
private val postHog: PostHogInterface,
) : AnalyticsTracker {
override val id: String = "posthog"

override fun track(event: AnalyticsEvent) {
val payload = event.toPayload()
postHog.capture(
event = payload.eventName,
properties = payload.properties.takeIf { it.isNotEmpty() },
)
}

override fun identify(identity: AnalyticsIdentity) {
postHog.identify(
distinctId = identity.distinctId,
userProperties = identity.toProperties().takeIf { it.isNotEmpty() },
)
}

override fun resetIdentity() {
postHog.reset()
}
}
68 changes: 68 additions & 0 deletions app/src/main/java/com/eatssu/android/di/AnalyticsModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.eatssu.android.di

import android.content.Context
import com.eatssu.android.BuildConfig
import com.eatssu.android.analytics.DefaultAnalyticsTracker
import com.eatssu.android.analytics.FirebaseAnalyticsTracker
import com.eatssu.android.analytics.PostHogAnalyticsTracker
import com.eatssu.common.analytics.AnalyticsTracker
import com.google.firebase.analytics.FirebaseAnalytics
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.posthog.PostHogInterface
import com.posthog.android.PostHogAndroid
import com.posthog.android.PostHogAndroidConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.IntoSet
import javax.inject.Singleton

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

@Provides
@Singleton
fun provideAnalyticsTracker(
defaultAnalyticsTracker: DefaultAnalyticsTracker,
): AnalyticsTracker = defaultAnalyticsTracker

@Provides
@IntoSet
fun provideFirebaseAnalyticsTracker(
firebaseAnalyticsTracker: FirebaseAnalyticsTracker,
): AnalyticsTracker = firebaseAnalyticsTracker

@Provides
@IntoSet
fun providePostHogAnalyticsTracker(
postHogAnalyticsTracker: PostHogAnalyticsTracker,
): AnalyticsTracker = postHogAnalyticsTracker

@Provides
@Singleton
fun provideFirebaseAnalytics(): FirebaseAnalytics {
return Firebase.analytics
}

@Provides
@Singleton
fun providePostHog(context: Context): PostHogInterface {
val config = PostHogAndroidConfig(
apiKey = BuildConfig.POSTHOG_API_KEY,
host = BuildConfig.POSTHOG_HOST,
).apply {
sessionReplay = true
sessionReplayConfig.screenshot = true
debug = BuildConfig.DEBUG
if (BuildConfig.DEBUG) {
sessionReplayConfig.maskAllTextInputs = false
sessionReplayConfig.maskAllImages = false
}
}

return PostHogAndroid.with(context, config)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ package com.eatssu.android.domain.usecase.auth
import com.eatssu.android.data.local.AccountDataStore
import com.eatssu.android.data.local.SettingDataStore
import com.eatssu.android.data.local.TokenStore
import com.eatssu.common.analytics.AnalyticsTracker
import javax.inject.Inject

class LogoutUseCase @Inject constructor(
private val accountDataStore: AccountDataStore,
private val tokenStore: TokenStore,
private val settingDataStore: SettingDataStore,
private val analyticsTracker: AnalyticsTracker,
) {
suspend operator fun invoke() {
accountDataStore.clear()
tokenStore.clear()
settingDataStore.clear()
analyticsTracker.resetIdentity()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.eatssu.android.domain.usecase.user

import com.eatssu.android.data.local.AccountDataStore
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class GetUserEmailUseCase @Inject constructor(
private val accountDataStore: AccountDataStore,
) {
suspend operator fun invoke(): String = accountDataStore.email.first()
}
Loading
Loading