From 260a2ffb168dc65f8fecd4bba43eeca431f5d2da Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 8 Apr 2025 13:17:09 +0200 Subject: [PATCH] Refactor analytics and handle opting out --- Habitica/AndroidManifest.xml | 1 + .../habitica/extensions/ResourcesExtensions.kt | 6 +++--- .../android/habitica/helpers/AdHandler.kt | 6 ------ .../android/habitica/helpers/Analytics.kt | 13 ++++++++++++- .../android/habitica/helpers/CrashReporter.kt | 14 ++++++++++++++ .../habitica/helpers/PurchaseHandler.kt | 7 +++---- .../interactors/ShowNotificationInteractor.kt | 11 ++++++----- .../habitica/models/user/Preferences.kt | 1 + .../habitica/ui/activities/MainActivity.kt | 11 +++++------ .../habitica/ui/fragments/AboutFragment.kt | 2 -- .../habitica/ui/fragments/BaseMainFragment.kt | 17 +++++++++++------ .../fragments/social/guilds/GuildFragment.kt | 7 ------- .../InsufficientGemsDialog.kt | 12 +++++++++--- .../habitica/ui/views/shops/PurchaseDialog.kt | 18 +++++++++++------- 14 files changed, 76 insertions(+), 50 deletions(-) create mode 100644 Habitica/src/main/java/com/habitrpg/android/habitica/helpers/CrashReporter.kt diff --git a/Habitica/AndroidManifest.xml b/Habitica/AndroidManifest.xml index ae0e89131..27168bf9b 100644 --- a/Habitica/AndroidManifest.xml +++ b/Habitica/AndroidManifest.xml @@ -26,6 +26,7 @@ + diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt index 757066880..4905d7cba 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/extensions/ResourcesExtensions.kt @@ -3,8 +3,8 @@ package com.habitrpg.android.habitica.extensions import android.content.res.Configuration import android.content.res.Resources import android.os.Build -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase +import com.habitrpg.android.habitica.helpers.CrashReporter + import com.habitrpg.android.habitica.ui.activities.BaseActivity import java.util.Locale @@ -19,7 +19,7 @@ fun Resources.forceLocale( updateConfiguration(configuration, displayMetrics) try { - Firebase.crashlytics.setCustomKey("language", locale.toLanguageTag()) + CrashReporter.setCustomKey("language", locale.toLanguageTag()) } catch (_: IllegalStateException) { // issue with getting firebase } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt index b25eb52f4..a8150f676 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/AdHandler.kt @@ -242,12 +242,6 @@ class AdHandler(val activity: Activity, val type: AdType, val rewardAction: (Boo "type" to type.name ) ) - FirebaseAnalytics.getInstance(activity).logEvent( - "adRewardEarned", - bundleOf( - Pair("type", type.name) - ) - ) rewardAction(true) }*/ } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt index f5af53090..44a79a9b9 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/Analytics.kt @@ -79,7 +79,8 @@ object Analytics { Amplitude( Configuration( context.getString(R.string.amplitude_app_id), - context + context, + optOut = true, ) ) firebase = FirebaseAnalytics.getInstance(context) @@ -126,4 +127,14 @@ object Analytics { fun logException(t: Throwable) { FirebaseCrashlytics.getInstance().recordException(t) } + + fun setAnalyticsConsent(consents: Boolean?) { + if (consents == true) { + firebase.setAnalyticsCollectionEnabled(true) + amplitude.configuration.optOut = false + } else { + firebase.setAnalyticsCollectionEnabled(false) + amplitude.configuration.optOut = true + } + } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/CrashReporter.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/CrashReporter.kt new file mode 100644 index 000000000..72bc974b2 --- /dev/null +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/CrashReporter.kt @@ -0,0 +1,14 @@ +package com.habitrpg.android.habitica.helpers + +import com.google.firebase.crashlytics.FirebaseCrashlytics +import com.google.firebase.crashlytics.ktx.crashlytics +import com.google.firebase.ktx.Firebase + +object CrashReporter { + fun setCustomKey(key: String, value: String) { + Firebase.crashlytics.setCustomKey(key, value) + } + fun recordException(throwable: Throwable) { + Firebase.crashlytics.recordException(throwable) + } +} diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt index 415eda1a3..ba9b2e638 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/helpers/PurchaseHandler.kt @@ -23,7 +23,6 @@ import com.android.billingclient.api.acknowledgePurchase import com.android.billingclient.api.consumePurchase import com.android.billingclient.api.queryProductDetails import com.android.billingclient.api.queryPurchasesAsync -import com.google.firebase.crashlytics.FirebaseCrashlytics import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.ApiClient @@ -122,7 +121,7 @@ class PurchaseHandler( } else -> { - FirebaseCrashlytics.getInstance().recordException(Throwable(result.debugMessage)) + CrashReporter.recordException(Throwable(result.debugMessage)) } } } @@ -291,7 +290,7 @@ class PurchaseHandler( } if (skuDetailsResult.billingResult.responseCode != BillingClient.BillingResponseCode.OK) { Log.e("PurchaseHandler", "Failed to load inventory: ${skuDetailsResult.billingResult.debugMessage}") - FirebaseCrashlytics.getInstance().recordException( + CrashReporter.recordException( Throwable( "Failed to load inventory: ${skuDetailsResult.billingResult.debugMessage}" ) @@ -496,7 +495,7 @@ class PurchaseHandler( } } processedPurchases.remove(purchase.orderId) - FirebaseCrashlytics.getInstance().recordException(throwable) + CrashReporter.recordException(throwable) } suspend fun checkForSubscription(): Purchase? { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt index 6f91d69bf..02e569dab 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/interactors/ShowNotificationInteractor.kt @@ -5,8 +5,11 @@ import android.view.LayoutInflater import android.view.View import android.widget.TextView import androidx.lifecycle.LifecycleCoroutineScope -import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R +import com.habitrpg.android.habitica.helpers.Analytics +import com.habitrpg.android.habitica.helpers.AnalyticsTarget +import com.habitrpg.android.habitica.helpers.EventCategory +import com.habitrpg.android.habitica.helpers.HitType import com.habitrpg.android.habitica.models.user.User import com.habitrpg.android.habitica.ui.views.HabiticaSnackbar import com.habitrpg.android.habitica.ui.views.SnackbarActivity @@ -196,10 +199,8 @@ class ShowNotificationInteractor( } private fun logOnboardingEvents(type: String) { - if (User.ONBOARDING_ACHIEVEMENT_KEYS.contains(type)) { - FirebaseAnalytics.getInstance(activity).logEvent(type, null) - } else if (type == Notification.Type.ACHIEVEMENT_ONBOARDING_COMPLETE.type) { - FirebaseAnalytics.getInstance(activity).logEvent(type, null) + if (User.ONBOARDING_ACHIEVEMENT_KEYS.contains(type) || type == Notification.Type.ACHIEVEMENT_ONBOARDING_COMPLETE.type) { + Analytics.sendEvent(type, EventCategory.BEHAVIOUR, HitType.EVENT, null, AnalyticsTarget.FIREBASE) } } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt index 9195d1b27..9c40fdc61 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/models/user/Preferences.kt @@ -45,4 +45,5 @@ open class Preferences : RealmObject(), AvatarPreferences, BaseObject { var emailNotifications: EmailNotificationsPreference? = null var autoEquip: Boolean = true var tasks: UserTaskPreferences? = null + var analyticsConsent: Boolean? = null } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt index 4d0398c5e..f0c524d41 100755 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/MainActivity.kt @@ -46,8 +46,6 @@ import androidx.navigation.NavDestination import androidx.navigation.findNavController import androidx.navigation.fragment.NavHostFragment import com.google.android.gms.wearable.Wearable -import com.google.firebase.crashlytics.ktx.crashlytics -import com.google.firebase.ktx.Firebase import com.google.firebase.perf.FirebasePerformance import com.habitrpg.android.habitica.BuildConfig import com.habitrpg.android.habitica.MainNavDirections @@ -60,6 +58,7 @@ import com.habitrpg.android.habitica.extensions.hideKeyboard import com.habitrpg.android.habitica.extensions.updateStatusBarColor import com.habitrpg.android.habitica.helpers.Analytics import com.habitrpg.android.habitica.helpers.AppConfigManager +import com.habitrpg.android.habitica.helpers.CrashReporter import com.habitrpg.android.habitica.helpers.EventCategory import com.habitrpg.android.habitica.helpers.HitType import com.habitrpg.android.habitica.helpers.NotificationOpenHandler @@ -644,9 +643,9 @@ open class MainActivity : BaseActivity(), SnackbarActivity { } preferences?.sound?.let { soundManager.soundTheme = it } - val crashlytics = Firebase.crashlytics - crashlytics.setCustomKey("day_start", user.preferences?.dayStart ?: 0) - crashlytics.setCustomKey("timezone_offset", user.preferences?.timezoneOffset ?: 0) + CrashReporter.setCustomKey("day_start", user.preferences?.dayStart ?: 0) + CrashReporter.setCustomKey("timezone_offset", user.preferences?.timezoneOffset ?: 0) + Analytics.setAnalyticsConsent(user.preferences?.analyticsConsent) displayDeathDialogIfNeeded() YesterdailyDialog.showDialogIfNeeded(this, user.id, userRepository, taskRepository) @@ -865,7 +864,7 @@ open class MainActivity : BaseActivity(), SnackbarActivity { errorJob?.cancel() } lifecycleScope.launch(Dispatchers.Main) { - if (binding.content.connectionIssueView.visibility == View.VISIBLE) { + if (binding.content.connectionIssueView.isVisible) { binding.content.connectionIssueView.visibility = View.GONE } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt index a8e3e625d..dff903e28 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/AboutFragment.kt @@ -10,7 +10,6 @@ import android.view.ViewGroup import android.view.animation.AccelerateInterpolator import android.widget.Toast import androidx.core.net.toUri -import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.FragmentAboutBinding import com.habitrpg.android.habitica.helpers.AppConfigManager @@ -148,7 +147,6 @@ class AboutFragment : BaseMainFragment() { private fun doTheThing() { val context = context ?: return - FirebaseAnalytics.getInstance(context).logEvent("found_easter_egg", null) DataBindingUtils.loadImage(context, "Pet-Sabretooth-Base") { bitmap -> mainActivity?.runOnUiThread { mainActivity?.let { diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt index 5c1fb0fee..5f868fad4 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/BaseMainFragment.kt @@ -19,6 +19,10 @@ import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.UserRepository import com.habitrpg.android.habitica.extensions.setNavigationBarDarkIcons +import com.habitrpg.android.habitica.helpers.Analytics +import com.habitrpg.android.habitica.helpers.AnalyticsTarget +import com.habitrpg.android.habitica.helpers.EventCategory +import com.habitrpg.android.habitica.helpers.HitType import com.habitrpg.android.habitica.helpers.SoundManager import com.habitrpg.android.habitica.ui.activities.MainActivity import com.habitrpg.android.habitica.ui.helpers.ToolbarColorHelper @@ -72,12 +76,13 @@ abstract class BaseMainFragment : BaseFragment() { showToolbar() enableToolbarScrolling() } - context?.let { - FirebaseAnalytics.getInstance(it).logEvent( - "fragment_view", - bundleOf(Pair("fragment", this::class.java.canonicalName)) - ) - } + Analytics.sendEvent( + "fragment_view", + EventCategory.NAVIGATION, + HitType.PAGEVIEW, + mapOf("fragment" to (this::class.java.canonicalName ?: "")), + AnalyticsTarget.FIREBASE + ) return super.onCreateView(inflater, container, savedInstanceState) } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt index 274453401..a191b2684 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/fragments/social/guilds/GuildFragment.kt @@ -15,7 +15,6 @@ import androidx.fragment.app.viewModels import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.tabs.TabLayoutMediator -import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.databinding.FragmentViewpagerBinding import com.habitrpg.android.habitica.models.social.Group @@ -71,12 +70,6 @@ class GuildFragment : BaseMainFragment() { setViewPagerAdapter() setFragments() - if (viewModel.groupID == "f2db2a7f-13c5-454d-b3ee-ea1f5089e601") { - context?.let { - FirebaseAnalytics.getInstance(it).logEvent("opened_no_party_guild", null) - } - } - viewModel.retrieveGroup { } } diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt index 6dcfb616f..7c746f440 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/insufficientCurrency/InsufficientGemsDialog.kt @@ -7,10 +7,13 @@ import android.widget.ProgressBar import android.widget.TextView import androidx.core.os.bundleOf import androidx.core.view.isVisible -import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.extensions.addCloseButton +import com.habitrpg.android.habitica.helpers.Analytics +import com.habitrpg.android.habitica.helpers.AnalyticsTarget import com.habitrpg.android.habitica.helpers.AppConfigManager +import com.habitrpg.android.habitica.helpers.EventCategory +import com.habitrpg.android.habitica.helpers.HitType import com.habitrpg.android.habitica.helpers.PurchaseHandler import com.habitrpg.android.habitica.helpers.PurchaseTypes import com.habitrpg.android.habitica.interactors.InsufficientGemsUseCase @@ -100,9 +103,12 @@ class InsufficientGemsDialog(val parentActivity: Activity, var gemPrice: Int) : purchaseButton.isVisible = true purchaseButton?.setOnClickListener { - FirebaseAnalytics.getInstance(context).logEvent( + Analytics.sendEvent( "purchased_gems_from_insufficient", - bundleOf(Pair("gemPrice", gemPrice), Pair("sku", "")) + EventCategory.BEHAVIOUR, + HitType.EVENT, + mapOf(Pair("gemPrice", gemPrice), Pair("sku", "")), + AnalyticsTarget.FIREBASE ) MainScope().launchCatching { insufficientGemsUseCase.callInteractor( diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt index 76e06dc2a..8f53115b7 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/views/shops/PurchaseDialog.kt @@ -3,6 +3,7 @@ package com.habitrpg.android.habitica.ui.views.shops import android.app.Activity import android.content.Context import android.graphics.drawable.BitmapDrawable +import android.media.metrics.Event import android.view.View import android.widget.ImageView import android.widget.LinearLayout @@ -12,7 +13,6 @@ import androidx.core.content.ContextCompat import androidx.core.graphics.drawable.toDrawable import androidx.core.os.bundleOf import androidx.lifecycle.lifecycleScope -import com.google.firebase.analytics.FirebaseAnalytics import com.habitrpg.android.habitica.HabiticaBaseApplication import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository @@ -23,6 +23,7 @@ import com.habitrpg.android.habitica.extensions.addCancelButton import com.habitrpg.android.habitica.extensions.addCloseButton import com.habitrpg.android.habitica.extensions.getShortRemainingString import com.habitrpg.android.habitica.helpers.Analytics +import com.habitrpg.android.habitica.helpers.AnalyticsTarget import com.habitrpg.android.habitica.helpers.AppConfigManager import com.habitrpg.android.habitica.helpers.EventCategory import com.habitrpg.android.habitica.helpers.HapticFeedbackManager @@ -437,13 +438,16 @@ class PurchaseDialog( } private fun buyItem(quantity: Int) { - FirebaseAnalytics.getInstance(context).logEvent( + Analytics.sendEvent( "item_purchased", - bundleOf( - Pair("shop", shopIdentifier), - Pair("type", shopItem.purchaseType), - Pair("key", shopItem.key) - ) + EventCategory.BEHAVIOUR, + HitType.EVENT, + mapOf( + "shop" to (shopIdentifier ?: ""), + "type" to shopItem.purchaseType, + "key" to shopItem.key + ), + AnalyticsTarget.FIREBASE ) HapticFeedbackManager.tap(buyButton) val snackbarText = arrayOf("")