From 99bed42e41c7e9ea6077c771b051d8e66ee5cfc0 Mon Sep 17 00:00:00 2001 From: Abhishek Pandey <64667840+1abhishekpandey@users.noreply.github.com> Date: Tue, 28 May 2024 22:32:12 +0530 Subject: [PATCH] feat: implement the analytics SDK as a singleton (#424) * feat: make createInstance return a singleton analytics instance * refactor: change createInstance to getInstance * chore: remove `getInstance` method * test: update the RudderAnalyticsTest test cases * chore(sample app): remove secondary analytics instance setup * chore: remove redundant else block * feat: implement double-checked locking for singleton analytics instance * chore: add custom annotation to indicate that AnalyticsRegistry could be used in the future * chore: refactor RudderAnalyticsBuilderCompat * chore(sample app): refactor sdk init snippet * chore: remove unnecessary comments * refactor: move FutureUse annotation inside `core` module So that it could have a greater visibility from other modules. * refactor: move AnalyticsUtil.kt into utilities package --- .../rudderstack/android/AnalyticsRegistry.kt | 5 +- .../rudderstack/android/RudderAnalytics.kt | 275 ++++-------------- .../compat/RudderAnalyticsBuilderCompat.java | 4 +- .../ActivityBroadcasterPlugin.kt | 2 +- .../infrastructure/AnonymousIdHeaderPlugin.kt | 2 +- .../AppInstallUpdateTrackerPlugin.kt | 4 +- .../infrastructure/LifecycleObserverPlugin.kt | 4 +- .../infrastructure/ReinstatePlugin.kt | 13 +- .../ResetImplementationPlugin.kt | 9 +- .../internal/plugins/ExtractStatePlugin.kt | 8 +- .../internal/plugins/FillDefaultsPlugin.kt | 8 +- .../internal/plugins/PlatformInputsPlugin.kt | 4 +- .../android/storage/RudderEntityFactory.kt | 2 - .../android/utilities/AnalyticsUtil.kt | 157 ++++++++++ .../android/utilities/SessionUtils.kt | 2 - .../android/RudderAnalyticsTest.kt | 137 +++------ .../ResetImplementationPluginTest.kt | 2 +- .../android/utilities/AnalyticsUtilTest.kt | 44 +++ .../android/utilities/SessionUtilsTest.kt | 4 - .../rudderstack/core/utilities/annotations.kt | 6 + .../android/navigationplugin/Lifecycle.kt | 2 +- .../internal/NavigationPlugin.kt | 2 +- .../android/sync/WorkerManagerPlugin.kt | 3 +- .../internal/DataSyncWorkManagerExtensions.kt | 2 +- .../android/sync/RudderSyncWorkerTest.kt | 2 +- .../sample-kotlin-android/build.gradle.kts | 12 - .../rudderstack.properties.sample | 4 - .../analytics/RudderAnalyticsUtils.kt | 74 ++--- .../android/sampleapp/analytics/Values.kt | 3 - .../SampleWorkManagerAnalyticsFactory.kt | 19 +- .../sampleapp/mainview/MainActivity.kt | 60 +--- .../sampleapp/mainview/MainViewModel.kt | 118 ++------ .../sampleapp/mainview/MainViewModelState.kt | 13 - 33 files changed, 386 insertions(+), 620 deletions(-) create mode 100644 android/src/main/java/com/rudderstack/android/utilities/AnalyticsUtil.kt create mode 100644 android/src/test/java/com/rudderstack/android/utilities/AnalyticsUtilTest.kt create mode 100644 core/src/main/java/com/rudderstack/core/utilities/annotations.kt diff --git a/android/src/main/java/com/rudderstack/android/AnalyticsRegistry.kt b/android/src/main/java/com/rudderstack/android/AnalyticsRegistry.kt index 02a898393..83eceb781 100644 --- a/android/src/main/java/com/rudderstack/android/AnalyticsRegistry.kt +++ b/android/src/main/java/com/rudderstack/android/AnalyticsRegistry.kt @@ -1,6 +1,7 @@ package com.rudderstack.android import androidx.annotation.VisibleForTesting +import com.rudderstack.core.utilities.FutureUse import com.rudderstack.core.Analytics import java.util.concurrent.ConcurrentHashMap @@ -18,6 +19,8 @@ import java.util.concurrent.ConcurrentHashMap * Note: The class is marked as internal, indicating that it is intended for use within the same module and should not be accessed * from outside the module. */ + +@FutureUse("This class will be utilized when multiple instances are implemented.") internal object AnalyticsRegistry { private val writeKeyToInstance: ConcurrentHashMap = ConcurrentHashMap() @@ -53,4 +56,4 @@ internal object AnalyticsRegistry { fun clear() { writeKeyToInstance.clear() } -} +} \ No newline at end of file diff --git a/android/src/main/java/com/rudderstack/android/RudderAnalytics.kt b/android/src/main/java/com/rudderstack/android/RudderAnalytics.kt index fec5dc930..f954df312 100644 --- a/android/src/main/java/com/rudderstack/android/RudderAnalytics.kt +++ b/android/src/main/java/com/rudderstack/android/RudderAnalytics.kt @@ -1,236 +1,63 @@ -/* - * Creator: Debanjan Chatterjee on 26/04/22, 3:08 PM Last modified: 26/04/22, 3:08 PM - * Copyright: All rights reserved Ⓒ 2022 http://rudderstack.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain a - * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ -@file:JvmName("RudderAnalytics") @file:Suppress("FunctionName") - package com.rudderstack.android -import com.rudderstack.android.internal.infrastructure.ActivityBroadcasterPlugin -import com.rudderstack.android.internal.infrastructure.AnonymousIdHeaderPlugin -import com.rudderstack.android.internal.infrastructure.AppInstallUpdateTrackerPlugin -import com.rudderstack.android.internal.infrastructure.LifecycleObserverPlugin -import com.rudderstack.android.internal.infrastructure.ResetImplementationPlugin -import com.rudderstack.android.internal.plugins.ExtractStatePlugin -import com.rudderstack.android.internal.plugins.FillDefaultsPlugin -import com.rudderstack.android.internal.plugins.PlatformInputsPlugin -import com.rudderstack.android.internal.infrastructure.ReinstatePlugin -import com.rudderstack.android.internal.plugins.SessionPlugin -import com.rudderstack.android.internal.states.ContextState -import com.rudderstack.android.internal.states.UserSessionState -import com.rudderstack.android.storage.AndroidStorage import com.rudderstack.android.storage.AndroidStorageImpl -import com.rudderstack.android.utilities.shutdownSessionManagement +import com.rudderstack.android.utilities.onShutdown +import com.rudderstack.android.utilities.startup import com.rudderstack.core.Analytics import com.rudderstack.core.ConfigDownloadService import com.rudderstack.core.DataUploadService -import com.rudderstack.core.holder.associateState -import com.rudderstack.core.holder.retrieveState -import com.rudderstack.models.MessageContext -import com.rudderstack.models.createContext -import com.rudderstack.models.traits -import com.rudderstack.models.updateWith - -//device info and stuff -//multi process -//bt stuff -//tv, -//work manager -private fun RudderAnalytics( - writeKey: String, - configuration: ConfigurationAndroid, - dataUploadService: DataUploadService? = null, - configDownloadService: ConfigDownloadService? = null, - storage: AndroidStorage = AndroidStorageImpl( - configuration.application, - writeKey = writeKey, - useContentProvider = ConfigurationAndroid.Defaults.USE_CONTENT_PROVIDER - ), - initializationListener: ((success: Boolean, message: String?) -> Unit)? = null -): Analytics { - return Analytics(writeKey, - configuration, - dataUploadService, - configDownloadService, - storage, - initializationListener = initializationListener, - shutdownHook = { - onShutdown() - }).apply { - startup() - } -} - -@JvmOverloads -fun createInstance( - writeKey: String, - configuration: ConfigurationAndroid, - dataUploadService: DataUploadService? = null, - configDownloadService: ConfigDownloadService? = null, - storage: AndroidStorage = AndroidStorageImpl( - configuration.application, - writeKey = writeKey, - useContentProvider = ConfigurationAndroid.Defaults.USE_CONTENT_PROVIDER - ), - initializationListener: ((success: Boolean, message: String?) -> Unit)? = null -): Analytics { - return AnalyticsRegistry.getInstance(writeKey) - ?: RudderAnalytics( - writeKey, - configuration, - dataUploadService, - configDownloadService, - storage, - initializationListener - ).also { analyticsInstance -> - AnalyticsRegistry.register(writeKey, analyticsInstance) - } -} - -fun getInstance(writeKey: String): Analytics? { - return AnalyticsRegistry.getInstance(writeKey) -} - -internal val Analytics.contextState: ContextState? - get() = retrieveState() -val Analytics.androidStorage: AndroidStorage - get() = (storage as AndroidStorage) +import com.rudderstack.core.Storage /** - * Set the AdvertisingId yourself. If set, SDK will not capture idfa automatically + * Singleton class for RudderAnalytics to manage the analytics instance. * - * @param advertisingId IDFA for the device + * This class ensures that only one instance of the Analytics object is created. */ -fun Analytics.putAdvertisingId(advertisingId: String) { - - applyConfiguration { - if (this is ConfigurationAndroid) copy( - advertisingId = advertisingId - ) - else this - } -} - -/** - * Set the push token for the device to be passed to the downstream destinations - * - * @param deviceToken Push Token from FCM - */ -fun Analytics.putDeviceToken(deviceToken: String) { - applyConfiguration { - if (this is ConfigurationAndroid) copy( - deviceToken = deviceToken - ) - else this - } -} - -/** - * Anonymous id to be used for all consecutive calls. - * Anonymous id is mostly used for messages sent prior to user identification or in case of - * anonymous usage. - * - * @param anonymousId String to be used as anonymousId - */ -fun Analytics.setAnonymousId(anonymousId: String) { - androidStorage.setAnonymousId(anonymousId) - applyConfiguration { - if (this is ConfigurationAndroid) copy( - anonymousId = anonymousId - ) - else this - } - val anonymousIdPair = ("anonymousId" to anonymousId) - val newContext = contextState?.value?.let { - it.updateWith(traits = (it.traits?: mapOf()) + anonymousIdPair) - }?: createContext(traits = mapOf(anonymousIdPair)) - processNewContext(newContext) -} - -/** - * Setting the [ConfigurationAndroid.userId] explicitly. - * - * @param userId String to be used as userId - */ -fun Analytics.setUserId(userId: String) { - androidStorage.setUserId(userId) - applyConfiguration { - if (this is ConfigurationAndroid) copy( - userId = userId - ) - else this - } -} - -private val infrastructurePlugins - get() = arrayOf( - ReinstatePlugin(), - AnonymousIdHeaderPlugin(), - AppInstallUpdateTrackerPlugin(), - LifecycleObserverPlugin(), - ActivityBroadcasterPlugin(), - ResetImplementationPlugin() - ) -private val messagePlugins - get() = listOf( - ExtractStatePlugin(), FillDefaultsPlugin(), PlatformInputsPlugin(), - SessionPlugin() - ) - -private fun Analytics.startup() { - addPlugins() - associateStates() -} - - -private fun Analytics.associateStates() { - associateState(ContextState()) - attachSavedContextIfAvailable() - associateState(UserSessionState()) -} - -private fun Analytics.attachSavedContextIfAvailable() { - androidStorage.context?.let { - processNewContext(it) - } -} - -private fun Analytics.addPlugins() { - addInfrastructurePlugin(*infrastructurePlugins) - addPlugin(*messagePlugins.toTypedArray()) -} - -internal fun Analytics.processNewContext( - newContext: MessageContext -) { - androidStorage.cacheContext(newContext) - contextState?.update(newContext) -} - -fun Analytics.applyConfigurationAndroid( - androidConfigurationScope: ConfigurationAndroid.() -> - ConfigurationAndroid -) { - applyConfiguration { - if (this is ConfigurationAndroid) androidConfigurationScope() - else this +class RudderAnalytics private constructor() { + + companion object { + + @Volatile + private var instance: Analytics? = null + + /** + * Returns the singleton instance of [Analytics], creating it if necessary. + * + * @param writeKey The write key for authentication. + * @param configuration The configuration settings for Android. + * @param storage The storage implementation for storing data. Defaults to [AndroidStorageImpl]. + * @param dataUploadService The service responsible for uploading data. Defaults to null. + * @param configDownloadService The service responsible for downloading configuration. Defaults to null. + * @param initializationListener A listener for initialization events. Defaults to null. + * @return The singleton instance of [Analytics]. + */ + @JvmStatic + @JvmOverloads + fun getInstance( + writeKey: String, + configuration: ConfigurationAndroid, + storage: Storage = AndroidStorageImpl( + configuration.application, + writeKey = writeKey, + useContentProvider = ConfigurationAndroid.Defaults.USE_CONTENT_PROVIDER + ), + dataUploadService: DataUploadService? = null, + configDownloadService: ConfigDownloadService? = null, + initializationListener: ((success: Boolean, message: String?) -> Unit)? = null, + ) = instance ?: synchronized(this) { + instance ?: Analytics( + writeKey = writeKey, + configuration = configuration, + dataUploadService = dataUploadService, + configDownloadService = configDownloadService, + storage = storage, + initializationListener = initializationListener, + shutdownHook = { onShutdown() } + ).apply { + startup() + }.also { + instance = it + } + } } } - -val Analytics.currentConfigurationAndroid: ConfigurationAndroid? - get() = (currentConfiguration as? ConfigurationAndroid) - -private fun Analytics.onShutdown() { - shutdownSessionManagement() -} - - diff --git a/android/src/main/java/com/rudderstack/android/compat/RudderAnalyticsBuilderCompat.java b/android/src/main/java/com/rudderstack/android/compat/RudderAnalyticsBuilderCompat.java index 1566635d1..8311f13ba 100644 --- a/android/src/main/java/com/rudderstack/android/compat/RudderAnalyticsBuilderCompat.java +++ b/android/src/main/java/com/rudderstack/android/compat/RudderAnalyticsBuilderCompat.java @@ -62,12 +62,12 @@ public RudderAnalyticsBuilderCompat withInitializationListener(InitializationLis } public Analytics build() { - return RudderAnalytics.createInstance( + return RudderAnalytics.getInstance( writeKey, configuration, + storage, dataUploadService, configDownloadService, - storage, (success, message) -> { if (initializationListener != null) { initializationListener.onInitialized(success, message); diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ActivityBroadcasterPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ActivityBroadcasterPlugin.kt index 7b7efc469..8614c66b4 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ActivityBroadcasterPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ActivityBroadcasterPlugin.kt @@ -18,7 +18,7 @@ import android.app.Activity import android.app.Application import android.os.Bundle import com.rudderstack.android.LifecycleListenerPlugin -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.core.Analytics import com.rudderstack.core.InfrastructurePlugin import java.util.concurrent.atomic.AtomicInteger diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/AnonymousIdHeaderPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/AnonymousIdHeaderPlugin.kt index 784b11ce0..7a6432f94 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/AnonymousIdHeaderPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/AnonymousIdHeaderPlugin.kt @@ -16,7 +16,7 @@ package com.rudderstack.android.internal.infrastructure import com.rudderstack.android.AndroidUtils import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.applyConfigurationAndroid +import com.rudderstack.android.utilities.applyConfigurationAndroid import com.rudderstack.core.Analytics import com.rudderstack.core.Configuration import com.rudderstack.core.DataUploadService diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/AppInstallUpdateTrackerPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/AppInstallUpdateTrackerPlugin.kt index 2e557b49b..ddef9799d 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/AppInstallUpdateTrackerPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/AppInstallUpdateTrackerPlugin.kt @@ -2,8 +2,8 @@ package com.rudderstack.android.internal.infrastructure import android.content.pm.PackageManager import android.os.Build -import com.rudderstack.android.androidStorage -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.androidStorage +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.android.storage.AndroidStorage import com.rudderstack.models.AppVersion import com.rudderstack.core.Analytics diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/LifecycleObserverPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/LifecycleObserverPlugin.kt index fd84d9a2f..1d525abcf 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/LifecycleObserverPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/LifecycleObserverPlugin.kt @@ -16,8 +16,8 @@ package com.rudderstack.android.internal.infrastructure import android.os.SystemClock import com.rudderstack.android.LifecycleListenerPlugin -import com.rudderstack.android.androidStorage -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.androidStorage +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.core.Analytics import com.rudderstack.core.ConfigDownloadService import com.rudderstack.core.Configuration diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ReinstatePlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ReinstatePlugin.kt index c738fcf6e..b1998c4b6 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ReinstatePlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ReinstatePlugin.kt @@ -17,12 +17,12 @@ package com.rudderstack.android.internal.infrastructure import android.content.Context import com.rudderstack.android.AndroidUtils import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.androidStorage -import com.rudderstack.android.contextState -import com.rudderstack.android.currentConfigurationAndroid -import com.rudderstack.android.processNewContext -import com.rudderstack.android.setAnonymousId -import com.rudderstack.android.setUserId +import com.rudderstack.android.utilities.androidStorage +import com.rudderstack.android.utilities.contextState +import com.rudderstack.android.utilities.currentConfigurationAndroid +import com.rudderstack.android.utilities.processNewContext +import com.rudderstack.android.utilities.setAnonymousId +import com.rudderstack.android.utilities.setUserId import com.rudderstack.android.utilities.initializeSessionManagement import com.rudderstack.android.utilities.isV1SavedServerConfigContainsSourceId import com.rudderstack.core.Analytics @@ -31,7 +31,6 @@ import com.rudderstack.core.DataUploadService import com.rudderstack.core.InfrastructurePlugin import com.rudderstack.models.RudderServerConfig import com.rudderstack.models.createContext -import com.rudderstack.models.traits import java.util.concurrent.atomic.AtomicBoolean /** diff --git a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPlugin.kt index 9a5e1dfa1..01c9b4989 100644 --- a/android/src/main/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPlugin.kt @@ -14,16 +14,11 @@ package com.rudderstack.android.internal.infrastructure -import com.rudderstack.android.contextState -import com.rudderstack.android.internal.states.ContextState -import com.rudderstack.android.processNewContext +import com.rudderstack.android.utilities.contextState +import com.rudderstack.android.utilities.processNewContext import com.rudderstack.core.Analytics import com.rudderstack.core.InfrastructurePlugin -import com.rudderstack.models.MessageContext import com.rudderstack.models.createContext -import com.rudderstack.models.customContexts -import com.rudderstack.models.externalIds -import com.rudderstack.models.traits import com.rudderstack.models.updateWith class ResetImplementationPlugin : InfrastructurePlugin { diff --git a/android/src/main/java/com/rudderstack/android/internal/plugins/ExtractStatePlugin.kt b/android/src/main/java/com/rudderstack/android/internal/plugins/ExtractStatePlugin.kt index a8b487480..0f52ce73a 100644 --- a/android/src/main/java/com/rudderstack/android/internal/plugins/ExtractStatePlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/plugins/ExtractStatePlugin.kt @@ -14,10 +14,10 @@ package com.rudderstack.android.internal.plugins -import com.rudderstack.android.contextState -import com.rudderstack.android.currentConfigurationAndroid -import com.rudderstack.android.processNewContext -import com.rudderstack.android.setUserId +import com.rudderstack.android.utilities.contextState +import com.rudderstack.android.utilities.currentConfigurationAndroid +import com.rudderstack.android.utilities.processNewContext +import com.rudderstack.android.utilities.setUserId import com.rudderstack.core.Analytics import com.rudderstack.core.Plugin import com.rudderstack.core.optAdd diff --git a/android/src/main/java/com/rudderstack/android/internal/plugins/FillDefaultsPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/plugins/FillDefaultsPlugin.kt index 7fc2aa0e2..7d1368417 100644 --- a/android/src/main/java/com/rudderstack/android/internal/plugins/FillDefaultsPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/plugins/FillDefaultsPlugin.kt @@ -14,14 +14,10 @@ package com.rudderstack.android.internal.plugins -import com.rudderstack.android.contextState -import com.rudderstack.android.currentConfigurationAndroid -import com.rudderstack.android.internal.states.ContextState +import com.rudderstack.android.utilities.contextState +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.core.Analytics -import com.rudderstack.core.Logger import com.rudderstack.core.Plugin -import com.rudderstack.core.Configuration -import com.rudderstack.core.State import com.rudderstack.core.MissingPropertiesException import com.rudderstack.core.minusWrtKeys import com.rudderstack.models.* diff --git a/android/src/main/java/com/rudderstack/android/internal/plugins/PlatformInputsPlugin.kt b/android/src/main/java/com/rudderstack/android/internal/plugins/PlatformInputsPlugin.kt index 3c0d7aaa4..bf5dfab6a 100644 --- a/android/src/main/java/com/rudderstack/android/internal/plugins/PlatformInputsPlugin.kt +++ b/android/src/main/java/com/rudderstack/android/internal/plugins/PlatformInputsPlugin.kt @@ -27,16 +27,14 @@ import android.telephony.TelephonyManager import com.rudderstack.android.AndroidUtils import com.rudderstack.android.AndroidUtils.isTv import com.rudderstack.android.LifecycleListenerPlugin -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.core.Analytics import com.rudderstack.core.Plugin import com.rudderstack.core.optAdd import com.rudderstack.models.Message import com.rudderstack.models.MessageContext -import com.rudderstack.rudderjsonadapter.RudderTypeAdapter import java.util.* import java.util.concurrent.atomic.AtomicReference -import kotlin.math.log /** * Sets the context specific to Android diff --git a/android/src/main/java/com/rudderstack/android/storage/RudderEntityFactory.kt b/android/src/main/java/com/rudderstack/android/storage/RudderEntityFactory.kt index c567d669d..cee14fa5a 100644 --- a/android/src/main/java/com/rudderstack/android/storage/RudderEntityFactory.kt +++ b/android/src/main/java/com/rudderstack/android/storage/RudderEntityFactory.kt @@ -14,10 +14,8 @@ package com.rudderstack.android.storage -import com.rudderstack.android.currentConfigurationAndroid import com.rudderstack.android.repository.Entity import com.rudderstack.android.repository.EntityFactory -import com.rudderstack.core.Analytics import com.rudderstack.rudderjsonadapter.JsonAdapter internal class RudderEntityFactory(private val jsonAdapter: JsonAdapter?) : EntityFactory { diff --git a/android/src/main/java/com/rudderstack/android/utilities/AnalyticsUtil.kt b/android/src/main/java/com/rudderstack/android/utilities/AnalyticsUtil.kt new file mode 100644 index 000000000..1f1cb00ec --- /dev/null +++ b/android/src/main/java/com/rudderstack/android/utilities/AnalyticsUtil.kt @@ -0,0 +1,157 @@ +@file:JvmName("AnalyticsUtil") @file:Suppress("FunctionName") + +package com.rudderstack.android.utilities + +import com.rudderstack.android.ConfigurationAndroid +import com.rudderstack.android.internal.infrastructure.ActivityBroadcasterPlugin +import com.rudderstack.android.internal.infrastructure.AnonymousIdHeaderPlugin +import com.rudderstack.android.internal.infrastructure.AppInstallUpdateTrackerPlugin +import com.rudderstack.android.internal.infrastructure.LifecycleObserverPlugin +import com.rudderstack.android.internal.infrastructure.ReinstatePlugin +import com.rudderstack.android.internal.infrastructure.ResetImplementationPlugin +import com.rudderstack.android.internal.plugins.ExtractStatePlugin +import com.rudderstack.android.internal.plugins.FillDefaultsPlugin +import com.rudderstack.android.internal.plugins.PlatformInputsPlugin +import com.rudderstack.android.internal.plugins.SessionPlugin +import com.rudderstack.android.internal.states.ContextState +import com.rudderstack.android.internal.states.UserSessionState +import com.rudderstack.android.storage.AndroidStorage +import com.rudderstack.core.Analytics +import com.rudderstack.core.holder.associateState +import com.rudderstack.core.holder.retrieveState +import com.rudderstack.models.MessageContext +import com.rudderstack.models.createContext +import com.rudderstack.models.traits +import com.rudderstack.models.updateWith + +val Analytics.currentConfigurationAndroid: ConfigurationAndroid? + get() = (currentConfiguration as? ConfigurationAndroid) + +internal val Analytics.contextState: ContextState? + get() = retrieveState() +val Analytics.androidStorage: AndroidStorage + get() = (storage as AndroidStorage) + +/** + * Set the AdvertisingId yourself. If set, SDK will not capture idfa automatically + * + * @param advertisingId IDFA for the device + */ +fun Analytics.putAdvertisingId(advertisingId: String) { + applyConfiguration { + if (this is ConfigurationAndroid) copy( + advertisingId = advertisingId + ) + else this + } +} + +/** + * Set the push token for the device to be passed to the downstream destinations + * + * @param deviceToken Push Token from FCM + */ +fun Analytics.putDeviceToken(deviceToken: String) { + applyConfiguration { + if (this is ConfigurationAndroid) copy( + deviceToken = deviceToken + ) + else this + } +} + +/** + * Anonymous id to be used for all consecutive calls. + * Anonymous id is mostly used for messages sent prior to user identification or in case of + * anonymous usage. + * + * @param anonymousId String to be used as anonymousId + */ +fun Analytics.setAnonymousId(anonymousId: String) { + androidStorage.setAnonymousId(anonymousId) + applyConfiguration { + if (this is ConfigurationAndroid) copy( + anonymousId = anonymousId + ) + else this + } + val anonymousIdPair = ("anonymousId" to anonymousId) + val newContext = contextState?.value?.let { + it.updateWith(traits = (it.traits ?: mapOf()) + anonymousIdPair) + } ?: createContext(traits = mapOf(anonymousIdPair)) + processNewContext(newContext) +} + +/** + * Setting the [ConfigurationAndroid.userId] explicitly. + * + * @param userId String to be used as userId + */ +fun Analytics.setUserId(userId: String) { + androidStorage.setUserId(userId) + applyConfiguration { + if (this is ConfigurationAndroid) copy( + userId = userId + ) + else this + } +} + +private val infrastructurePlugins = arrayOf( + ReinstatePlugin(), + AnonymousIdHeaderPlugin(), + AppInstallUpdateTrackerPlugin(), + LifecycleObserverPlugin(), + ActivityBroadcasterPlugin(), + ResetImplementationPlugin() +) + +private val messagePlugins = listOf( + ExtractStatePlugin(), + FillDefaultsPlugin(), + PlatformInputsPlugin(), + SessionPlugin() +) + +internal fun Analytics.startup() { + addPlugins() + associateStates() +} + + +fun Analytics.applyConfigurationAndroid( + androidConfigurationScope: ConfigurationAndroid.() -> + ConfigurationAndroid +) { + applyConfiguration { + if (this is ConfigurationAndroid) androidConfigurationScope() + else this + } +} + +internal fun Analytics.processNewContext( + newContext: MessageContext +) { + androidStorage.cacheContext(newContext) + contextState?.update(newContext) +} + +internal fun Analytics.onShutdown() { + shutdownSessionManagement() +} +private fun Analytics.addPlugins() { + addInfrastructurePlugin(*infrastructurePlugins) + addPlugin(*messagePlugins.toTypedArray()) +} + +private fun Analytics.associateStates() { + associateState(ContextState()) + attachSavedContextIfAvailable() + associateState(UserSessionState()) +} + +private fun Analytics.attachSavedContextIfAvailable() { + androidStorage.context?.let { + processNewContext(it) + } +} \ No newline at end of file diff --git a/android/src/main/java/com/rudderstack/android/utilities/SessionUtils.kt b/android/src/main/java/com/rudderstack/android/utilities/SessionUtils.kt index 95cf1c9cd..359016cfb 100644 --- a/android/src/main/java/com/rudderstack/android/utilities/SessionUtils.kt +++ b/android/src/main/java/com/rudderstack/android/utilities/SessionUtils.kt @@ -15,8 +15,6 @@ package com.rudderstack.android.utilities import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.androidStorage -import com.rudderstack.android.currentConfigurationAndroid import com.rudderstack.android.internal.states.UserSessionState import com.rudderstack.core.Analytics import com.rudderstack.core.holder.retrieveState diff --git a/android/src/test/java/com/rudderstack/android/RudderAnalyticsTest.kt b/android/src/test/java/com/rudderstack/android/RudderAnalyticsTest.kt index cb51106b3..c6e6591c0 100644 --- a/android/src/test/java/com/rudderstack/android/RudderAnalyticsTest.kt +++ b/android/src/test/java/com/rudderstack/android/RudderAnalyticsTest.kt @@ -1,129 +1,74 @@ -/* - * Creator: Debanjan Chatterjee on 11/12/23, 11:14 am Last modified: 11/12/23, 11:14 am - * Copyright: All rights reserved Ⓒ 2023 http://rudderstack.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain a - * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - package com.rudderstack.android import android.os.Build import androidx.test.core.app.ApplicationProvider +import com.rudderstack.android.RudderAnalytics.Companion.getInstance import com.rudderstack.core.Analytics import com.rudderstack.jacksonrudderadapter.JacksonAdapter import org.hamcrest.MatcherAssert import org.hamcrest.Matchers -import org.hamcrest.Matchers.allOf -import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner import org.robolectric.annotation.Config -@RunWith( - RobolectricTestRunner::class) -@Config(manifest = Config.NONE, sdk = [Build.VERSION_CODES.P])class RudderAnalyticsTest { +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, sdk = [Build.VERSION_CODES.P]) +class RudderAnalyticsTest { val writeKey = "writeKey" - @Before - fun setUp() { - AnalyticsRegistry.clear() - } - @Test - fun `test put anonymous id`() { - val analytics = createInstance("testKey", ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - analytics.setAnonymousId("anon_id") - MatcherAssert.assertThat( - analytics.currentConfigurationAndroid, allOf(Matchers.isA(ConfigurationAndroid::class.java), - Matchers.hasProperty("anonymousId", Matchers.equalTo("anon_id")) - )) - } - - @Test - fun `when writeKey and configuration is passed, then createInstance should return Analytics instance`() { - val analytics = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) + fun `when writeKey and configuration is passed, then getInstance should return Analytics instance`() { + val analytics = getInstance( + writeKey, ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) MatcherAssert.assertThat(analytics, Matchers.isA(Analytics::class.java)) } @Test - fun `when multiple instances are created with different writeKeys, then the instances should be different`() { + fun `given that the SDK supports a singleton instance, when an attempt is made to create multiple instance with the different writeKey, then both instances should remain the same`() { val writeKey2 = "writeKey2" - val analytics = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - val analytics2 = createInstance(writeKey2, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) + val analytics = getInstance( + writeKey, ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) + + val analytics2 = getInstance( + writeKey2, ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) MatcherAssert.assertThat(analytics, Matchers.isA(Analytics::class.java)) MatcherAssert.assertThat(analytics2, Matchers.isA(Analytics::class.java)) - assert(analytics != analytics2) + assert(analytics == analytics2) } @Test - fun `given instance is already created with the writeKey, when createInstance is called with same write key, then the previous instance should be returned`() { - val analytics = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - val analytics2 = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) + fun `given that the SDK supports a singleton instance, when an attempt is made to create multiple instance with the same writeKey, then both instances should remain the same`() { + val analytics = getInstance( + writeKey, ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) + + val analytics2 = getInstance( + writeKey, ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) MatcherAssert.assertThat(analytics, Matchers.isA(Analytics::class.java)) MatcherAssert.assertThat(analytics2, Matchers.isA(Analytics::class.java)) assert(analytics == analytics2) } - - @Test - fun `given instance is already created with the writeKey, when getInstance is called with that write key, then the Analytics instance should be returned`() { - val analytics = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - val result = AnalyticsRegistry.getInstance(writeKey) - assert(result == analytics) - } - - @Test - fun `given multiple instances are already created with different writeKeys, when getInstance is called with those write keys, then the Analytics instances should be returned`() { - val writeKey2 = "writeKey2" - val analytics = createInstance(writeKey, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - val analytics2 = createInstance(writeKey2, ConfigurationAndroid( - ApplicationProvider.getApplicationContext(), - JacksonAdapter() - )) - - val result1 = AnalyticsRegistry.getInstance(writeKey) - val result2 = AnalyticsRegistry.getInstance(writeKey2) - - assert(result1 == analytics) - assert(result2 == analytics2) - } -} +} \ No newline at end of file diff --git a/android/src/test/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPluginTest.kt b/android/src/test/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPluginTest.kt index a1ad5d295..a371c6c44 100644 --- a/android/src/test/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPluginTest.kt +++ b/android/src/test/java/com/rudderstack/android/internal/infrastructure/ResetImplementationPluginTest.kt @@ -3,7 +3,7 @@ package com.rudderstack.android.internal.infrastructure import android.os.Build import androidx.test.core.app.ApplicationProvider import com.rudderstack.android.utils.TestExecutor -import com.rudderstack.android.contextState +import com.rudderstack.android.utilities.contextState import com.rudderstack.android.internal.states.ContextState import com.rudderstack.android.storage.AndroidStorageImpl import com.rudderstack.core.Analytics diff --git a/android/src/test/java/com/rudderstack/android/utilities/AnalyticsUtilTest.kt b/android/src/test/java/com/rudderstack/android/utilities/AnalyticsUtilTest.kt new file mode 100644 index 000000000..ebea0f61a --- /dev/null +++ b/android/src/test/java/com/rudderstack/android/utilities/AnalyticsUtilTest.kt @@ -0,0 +1,44 @@ +package com.rudderstack.android.utilities + +import android.os.Build +import androidx.test.core.app.ApplicationProvider +import com.rudderstack.android.AnalyticsRegistry +import com.rudderstack.android.ConfigurationAndroid +import com.rudderstack.android.RudderAnalytics.Companion.getInstance +import com.rudderstack.jacksonrudderadapter.JacksonAdapter +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers +import org.hamcrest.Matchers.allOf +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(manifest = Config.NONE, sdk = [Build.VERSION_CODES.P]) +class AnalyticsUtilTest { + + @Before + fun setUp() { + AnalyticsRegistry.clear() + } + + @Test + fun `given writeKey and configuration are passed, when anonymousId id is set, then assert that configuration has this anonymousId set as a property`() { + val analytics = getInstance( + "testKey", ConfigurationAndroid( + ApplicationProvider.getApplicationContext(), + JacksonAdapter() + ) + ) + + analytics.setAnonymousId("anon_id") + MatcherAssert.assertThat( + analytics.currentConfigurationAndroid, allOf( + Matchers.isA(ConfigurationAndroid::class.java), + Matchers.hasProperty("anonymousId", Matchers.equalTo("anon_id")) + ) + ) + } +} diff --git a/android/src/test/java/com/rudderstack/android/utilities/SessionUtilsTest.kt b/android/src/test/java/com/rudderstack/android/utilities/SessionUtilsTest.kt index 1988da599..e33aa8d63 100644 --- a/android/src/test/java/com/rudderstack/android/utilities/SessionUtilsTest.kt +++ b/android/src/test/java/com/rudderstack/android/utilities/SessionUtilsTest.kt @@ -3,8 +3,6 @@ package com.rudderstack.android.utilities import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.androidStorage -import com.rudderstack.android.currentConfigurationAndroid import com.rudderstack.android.internal.states.UserSessionState import com.rudderstack.android.storage.AndroidStorage import com.rudderstack.core.Analytics @@ -18,7 +16,6 @@ import com.rudderstack.rudderjsonadapter.JsonAdapter import com.vagabond.testcommon.generateTestAnalytics import org.hamcrest.MatcherAssert import org.hamcrest.Matchers.allOf -import org.hamcrest.Matchers.contains import org.hamcrest.Matchers.hasItem import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.not @@ -30,7 +27,6 @@ import org.junit.runner.RunWith import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.atLeast import org.mockito.kotlin.mock -import org.mockito.kotlin.times import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.robolectric.annotation.Config diff --git a/core/src/main/java/com/rudderstack/core/utilities/annotations.kt b/core/src/main/java/com/rudderstack/core/utilities/annotations.kt new file mode 100644 index 000000000..2618209fd --- /dev/null +++ b/core/src/main/java/com/rudderstack/core/utilities/annotations.kt @@ -0,0 +1,6 @@ +package com.rudderstack.core.utilities + + +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS) +annotation class FutureUse(val info: String) \ No newline at end of file diff --git a/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/Lifecycle.kt b/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/Lifecycle.kt index c600a5e0b..7a977991f 100644 --- a/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/Lifecycle.kt +++ b/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/Lifecycle.kt @@ -14,7 +14,7 @@ package com.rudderstack.android.navigationplugin import androidx.navigation.NavController -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.android.navigationplugin.internal.NavControllerState import com.rudderstack.android.navigationplugin.internal.NavigationPlugin import com.rudderstack.core.Analytics diff --git a/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/internal/NavigationPlugin.kt b/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/internal/NavigationPlugin.kt index 2bd3d3033..680776f7f 100644 --- a/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/internal/NavigationPlugin.kt +++ b/libs/navigationplugin/src/main/java/com/rudderstack/android/navigationplugin/internal/NavigationPlugin.kt @@ -18,7 +18,7 @@ import android.os.Bundle import androidx.navigation.NavController import androidx.navigation.NavDestination import com.rudderstack.android.LifecycleListenerPlugin -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.core.Analytics import com.rudderstack.core.InfrastructurePlugin import com.rudderstack.core.State diff --git a/libs/sync/src/main/java/com/rudderstack/android/sync/WorkerManagerPlugin.kt b/libs/sync/src/main/java/com/rudderstack/android/sync/WorkerManagerPlugin.kt index e8822c039..407692f4a 100644 --- a/libs/sync/src/main/java/com/rudderstack/android/sync/WorkerManagerPlugin.kt +++ b/libs/sync/src/main/java/com/rudderstack/android/sync/WorkerManagerPlugin.kt @@ -15,11 +15,10 @@ package com.rudderstack.android.sync import android.app.Application -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.android.sync.internal.registerWorkManager import com.rudderstack.android.sync.internal.unregisterWorkManager import com.rudderstack.core.Analytics -import com.rudderstack.core.Configuration import com.rudderstack.core.InfrastructurePlugin abstract class WorkerManagerPlugin : InfrastructurePlugin { diff --git a/libs/sync/src/main/java/com/rudderstack/android/sync/internal/DataSyncWorkManagerExtensions.kt b/libs/sync/src/main/java/com/rudderstack/android/sync/internal/DataSyncWorkManagerExtensions.kt index 5b730621e..0c955b719 100644 --- a/libs/sync/src/main/java/com/rudderstack/android/sync/internal/DataSyncWorkManagerExtensions.kt +++ b/libs/sync/src/main/java/com/rudderstack/android/sync/internal/DataSyncWorkManagerExtensions.kt @@ -24,7 +24,7 @@ import androidx.work.NetworkType import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import androidx.work.multiprocess.RemoteWorkManager -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.android.sync.WorkManagerAnalyticsFactory import com.rudderstack.core.Analytics import java.lang.ref.WeakReference diff --git a/libs/sync/src/test/java/com/rudderstack/android/sync/RudderSyncWorkerTest.kt b/libs/sync/src/test/java/com/rudderstack/android/sync/RudderSyncWorkerTest.kt index 9c8f74e4d..c28246762 100644 --- a/libs/sync/src/test/java/com/rudderstack/android/sync/RudderSyncWorkerTest.kt +++ b/libs/sync/src/test/java/com/rudderstack/android/sync/RudderSyncWorkerTest.kt @@ -20,7 +20,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.work.ListenableWorker import androidx.work.testing.TestWorkerBuilder import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.currentConfigurationAndroid +import com.rudderstack.android.utilities.currentConfigurationAndroid import com.rudderstack.android.sync.internal.RudderSyncWorker import com.rudderstack.android.sync.internal.workerInputData import com.rudderstack.android.sync.utils.TestLogger diff --git a/samples/sample-kotlin-android/build.gradle.kts b/samples/sample-kotlin-android/build.gradle.kts index c858b9b2e..2a0410c3d 100644 --- a/samples/sample-kotlin-android/build.gradle.kts +++ b/samples/sample-kotlin-android/build.gradle.kts @@ -42,26 +42,14 @@ android { "String", "WRITE_KEY", sampleRudderProperties.getProperty("writeKey") ) - buildConfigField( - "String", "WRITE_KEY_SECONDARY", - sampleRudderProperties.getProperty("writeKeySecondary") - ) buildConfigField( "String", "CONTROL_PLANE_URL", sampleRudderProperties.getProperty("controlplaneUrl") ) - buildConfigField( - "String", "CONTROL_PLANE_URL_SECONDARY", - sampleRudderProperties.getProperty("controlplaneUrlSecondary") - ) buildConfigField( "String", "DATA_PLANE_URL", sampleRudderProperties.getProperty("dataplaneUrl") ) - buildConfigField( - "String", "DATA_PLANE_URL_SECONDARY", - sampleRudderProperties.getProperty("dataplaneUrlSecondary") - ) } buildTypes { diff --git a/samples/sample-kotlin-android/rudderstack.properties.sample b/samples/sample-kotlin-android/rudderstack.properties.sample index ef8bf31c0..b52d1b5b4 100644 --- a/samples/sample-kotlin-android/rudderstack.properties.sample +++ b/samples/sample-kotlin-android/rudderstack.properties.sample @@ -7,7 +7,3 @@ writeKey="" controlplaneUrl="" dataplaneUrl="" -# For a secondary Rudderstack instance, replace with your other Rudderstack values -writeKeySecondary="" -controlplaneUrlSecondary="" -dataplaneUrlSecondary="" diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/RudderAnalyticsUtils.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/RudderAnalyticsUtils.kt index 6b6da81ee..8c9e93ae4 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/RudderAnalyticsUtils.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/RudderAnalyticsUtils.kt @@ -3,7 +3,7 @@ package com.rudderstack.android.sampleapp.analytics import android.app.Application import com.rudderstack.android.BuildConfig import com.rudderstack.android.ConfigurationAndroid -import com.rudderstack.android.createInstance +import com.rudderstack.android.RudderAnalytics.Companion.getInstance import com.rudderstack.android.ruddermetricsreporterandroid.Configuration import com.rudderstack.android.ruddermetricsreporterandroid.DefaultRudderReporter import com.rudderstack.android.ruddermetricsreporterandroid.LibraryMetadata @@ -15,48 +15,18 @@ import com.rudderstack.jacksonrudderadapter.JacksonAdapter object RudderAnalyticsUtils { - private var _rudderAnalytics: Analytics? = null - private var _rudderAnalyticsSecondary: Analytics? = null + val analytics: Analytics + get() = _rudderAnalytics ?: throw IllegalStateException( + "Rudder Analytics Primary not " + "initialized" + ) + private var _rudderReporter: RudderReporter? = null + val reporter: RudderReporter? get() = _rudderReporter fun initialize(application: Application, listener: InitializationListener? = null) { //wen add work manager support to this instance - _rudderAnalytics = createPrimaryAnalyticsInstanceWithWorkerSupport(application, listener) - _rudderAnalyticsSecondary = createSecondaryInstance(listener, application) - _rudderReporter = DefaultRudderReporter( - context = application, baseUrl = METRICS_BASE_URL, configuration = Configuration( - LibraryMetadata( - name = "android", - sdkVersion = BuildConfig.LIBRARY_PACKAGE_NAME, - versionCode = BuildConfig.LIBRARY_VERSION_NAME, - writeKey = WRITE_KEY - ) - ), JacksonAdapter() - ) - _rudderAnalytics?.initializeWorkManager() - } - - private fun createSecondaryInstance( - listener: InitializationListener?, - application: Application - ) = createInstance( - writeKey = WRITE_KEY_SECONDARY, - initializationListener = { success, message -> - listener?.onAnalyticsInitialized(WRITE_KEY_SECONDARY, success, message) - }, - configuration = ConfigurationAndroid( - application = application, - GsonAdapter(), - dataPlaneUrl = DATA_PLANE_URL_SECONDARY, - controlPlaneUrl = CONTROL_PLANE_URL_SECONDARY, - trackLifecycleEvents = true, - recordScreenViews = true, - ) - ) - - fun createPrimaryAnalyticsInstanceWithWorkerSupport(application: Application, listener: InitializationListener? = null): Analytics { - return createInstance( + _rudderAnalytics = getInstance( writeKey = WRITE_KEY, initializationListener = { success, message -> listener?.onAnalyticsInitialized(WRITE_KEY, success, message) @@ -69,27 +39,19 @@ object RudderAnalyticsUtils { trackLifecycleEvents = true, recordScreenViews = true, isPeriodicFlushEnabled = true - ) ) - } - private fun Analytics.initializeWorkManager() { - addInfrastructurePlugin(SampleWorkManagerPlugin()) - } - - val primaryAnalytics: Analytics - get() = _rudderAnalytics ?: throw IllegalStateException( - "Rudder Analytics Primary not " + "initialized" - ) - - val secondaryAnalytics: Analytics - get() = _rudderAnalyticsSecondary ?: throw IllegalStateException( - "Rudder Analytics " + "Secondary" + " not initialized" + _rudderReporter = DefaultRudderReporter( + context = application, baseUrl = METRICS_BASE_URL, configuration = Configuration( + LibraryMetadata( + name = "android", + sdkVersion = BuildConfig.LIBRARY_PACKAGE_NAME, + versionCode = BuildConfig.LIBRARY_VERSION_NAME, + writeKey = WRITE_KEY + ) + ), JacksonAdapter() ) - - - fun getReporter(): RudderReporter? { - return _rudderReporter + _rudderAnalytics?.addInfrastructurePlugin(SampleWorkManagerPlugin()) } fun interface InitializationListener { diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/Values.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/Values.kt index 690feb41e..70dac9580 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/Values.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/Values.kt @@ -5,7 +5,4 @@ import com.rudderstack.android.sampleapp.BuildConfig const val WRITE_KEY = BuildConfig.WRITE_KEY const val DATA_PLANE_URL = BuildConfig.DATA_PLANE_URL const val CONTROL_PLANE_URL = BuildConfig.CONTROL_PLANE_URL -const val WRITE_KEY_SECONDARY = BuildConfig.WRITE_KEY_SECONDARY -const val DATA_PLANE_URL_SECONDARY = BuildConfig.DATA_PLANE_URL_SECONDARY -const val CONTROL_PLANE_URL_SECONDARY = BuildConfig.CONTROL_PLANE_URL_SECONDARY const val METRICS_BASE_URL = "BASE_URL" diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/workmanger/SampleWorkManagerAnalyticsFactory.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/workmanger/SampleWorkManagerAnalyticsFactory.kt index a19e1f07a..8dd6441ee 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/workmanger/SampleWorkManagerAnalyticsFactory.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/analytics/workmanger/SampleWorkManagerAnalyticsFactory.kt @@ -1,17 +1,3 @@ -/* - * Creator: Debanjan Chatterjee on 22/03/24, 1:06 pm Last modified: 22/03/24, 1:06 pm - * Copyright: All rights reserved Ⓒ 2024 http://rudderstack.com - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain a - * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - package com.rudderstack.android.sampleapp.analytics.workmanger import android.app.Application @@ -20,7 +6,8 @@ import com.rudderstack.android.sync.WorkManagerAnalyticsFactory import com.rudderstack.core.Analytics class SampleWorkManagerAnalyticsFactory : WorkManagerAnalyticsFactory { + override fun createAnalytics(application: Application): Analytics { - return RudderAnalyticsUtils.createPrimaryAnalyticsInstanceWithWorkerSupport(application) + return RudderAnalyticsUtils.analytics } -} \ No newline at end of file +} diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainActivity.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainActivity.kt index 873f6d198..d318935f7 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainActivity.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainActivity.kt @@ -102,8 +102,15 @@ class MainActivity : ComponentActivity() { .padding(8.dp), horizontalAlignment = Alignment.CenterHorizontally ) { - Text(text = "Primary Analytics", modifier = Modifier.padding(2.dp).align(Alignment - .CenterHorizontally), ) + Text( + text = "Singleton Analytics", + modifier = Modifier + .padding(2.dp) + .align( + Alignment + .CenterHorizontally + ), + ) CreateRowOfApis( names = arrayOf( AnalyticsState.ShutDownAnalytics, @@ -112,9 +119,11 @@ class MainActivity : ComponentActivity() { ), weight = .5f, viewModel = viewModel ) CreateRowOfApis( - names = arrayOf(AnalyticsState.AliasEvent, + names = arrayOf( + AnalyticsState.AliasEvent, AnalyticsState.TrackEvent, - AnalyticsState.ScreenEvent), + AnalyticsState.ScreenEvent + ), weight = .3f, viewModel = viewModel ) @@ -133,50 +142,11 @@ class MainActivity : ComponentActivity() { weight = .5f, viewModel = viewModel ) - Text(text = "Secondary Analytics", modifier = Modifier.padding(8.dp).align(Alignment - .CenterHorizontally), ) - Spacer(modifier = Modifier.height(2.dp)) CreateRowOfApis( names = arrayOf( - AnalyticsState.ShutDownAnalyticsSecondary, - AnalyticsState.ForceFlushSecondary, - AnalyticsState.OptInAnalyticsSecondary - ), weight = .3f, viewModel = viewModel - ) - Spacer(modifier = Modifier.height(2.dp)) - CreateRowOfApis( - names = arrayOf(AnalyticsState.AliasEventSecondary, - AnalyticsState.TrackEventSecondary, - AnalyticsState.ScreenEventSecondary), - weight = .3f, - viewModel = viewModel - ) - Spacer(modifier = Modifier.height(2.dp)) - CreateRowOfApis( - names = arrayOf(AnalyticsState.IdentifyEventSecondary, AnalyticsState - .GroupEventSecondary), - weight = .5f, - viewModel = viewModel - ) - Spacer(modifier = Modifier.height(2.dp)) - - CreateRowOfApis( - names = arrayOf(AnalyticsState.StartManualSessionSecondary, AnalyticsState - .EndSessionSecondary), - weight = .5f, - viewModel = viewModel - ) - Spacer(modifier = Modifier.height(2.dp)) - CreateRowOfApis( - names = arrayOf(AnalyticsState.EnableAutoTrackingSecondary, AnalyticsState - .DisableAutoTrackingSecondary), - weight = .5f, - viewModel = viewModel - ) - CreateRowOfApis( - names = arrayOf(AnalyticsState.SendError, + AnalyticsState.SendError, AnalyticsState.ClearAnalytics, - ), + ), weight = .5f, viewModel = viewModel ) diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModel.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModel.kt index 8288814c8..abeb4c4e8 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModel.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModel.kt @@ -2,10 +2,9 @@ package com.rudderstack.android.sampleapp.mainview import android.app.Application import androidx.lifecycle.AndroidViewModel -import com.rudderstack.android.applyConfigurationAndroid +import com.rudderstack.android.utilities.applyConfigurationAndroid import com.rudderstack.android.sampleapp.analytics.RudderAnalyticsUtils -import com.rudderstack.android.sampleapp.analytics.RudderAnalyticsUtils.primaryAnalytics -import com.rudderstack.android.sampleapp.analytics.RudderAnalyticsUtils.secondaryAnalytics +import com.rudderstack.android.sampleapp.analytics.RudderAnalyticsUtils.analytics import com.rudderstack.android.utilities.endSession import com.rudderstack.android.utilities.startSession import com.rudderstack.core.Analytics @@ -26,7 +25,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { private val _state = MutableStateFlow(MainViewModelState()) val state = _state.asStateFlow() - private var _rudderReporter = RudderAnalyticsUtils.getReporter() + private var _rudderReporter = RudderAnalyticsUtils.reporter private val _loggingInterceptor by lazy { object : Plugin { @@ -53,19 +52,18 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } init { - primaryAnalytics.addPlugin(_loggingInterceptor) - secondaryAnalytics.addPlugin(_loggingInterceptor) + analytics.addPlugin(_loggingInterceptor) } internal fun onEventClicked(analytics: AnalyticsState) { val log = when (analytics) { AnalyticsState.ShutDownAnalytics -> { - primaryAnalytics.shutdown() + RudderAnalyticsUtils.analytics.shutdown() "Rudder Analytics is shutting down. Init again if needed. This might take a second" } AnalyticsState.TrackEvent -> { - primaryAnalytics.track( + RudderAnalyticsUtils.analytics.track( eventName = "Track at ${Date()}", trackProperties = TrackProperties("key1" to "prop1", "key2" to "prop2"), options = RudderOptions.Builder().withIntegrations(mapOf("firebase" to false)) @@ -77,19 +75,19 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } AnalyticsState.IdentifyEvent -> { - primaryAnalytics.identify( + RudderAnalyticsUtils.analytics.identify( userId = "some_user_id", traits = IdentifyTraits("trait1" to "some_trait") ) "Identify called" } AnalyticsState.AliasEvent -> { - primaryAnalytics.alias(newId = "user_new_id") + RudderAnalyticsUtils.analytics.alias(newId = "user_new_id") "Alias called" } AnalyticsState.GroupEvent -> { - primaryAnalytics.group( + RudderAnalyticsUtils.analytics.group( groupId = "group_id", groupTraits = GroupTraits("g_t1" to "t-1", "g_t2" to "t-2"), ) @@ -97,7 +95,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } AnalyticsState.ScreenEvent -> { - primaryAnalytics.screen( + RudderAnalyticsUtils.analytics.screen( screenName = "some_screen", category = "some_category", screenProperties = ScreenProperties() @@ -115,115 +113,36 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } AnalyticsState.OptInAnalytics -> { - primaryAnalytics.optOut(primaryAnalytics.isOptedOut) - "OPT ${if (primaryAnalytics.isOptedOut) "out" else "in"} pressed" + RudderAnalyticsUtils.analytics.optOut(RudderAnalyticsUtils.analytics.isOptedOut) + "OPT ${if (RudderAnalyticsUtils.analytics.isOptedOut) "out" else "in"} pressed" } AnalyticsState.ForceFlush -> { - primaryAnalytics.flush() + RudderAnalyticsUtils.analytics.flush() "Forcing a flush" } AnalyticsState.EnableAutoTracking -> { - primaryAnalytics.applyConfigurationAndroid { + RudderAnalyticsUtils.analytics.applyConfigurationAndroid { copy(trackAutoSession = true) } "Auto tracking enabled" } AnalyticsState.DisableAutoTracking -> { - primaryAnalytics.applyConfigurationAndroid { + RudderAnalyticsUtils.analytics.applyConfigurationAndroid { copy(trackAutoSession = false) } "Auto tracking disabled" } AnalyticsState.StartManualSession -> { - primaryAnalytics.startSession() + RudderAnalyticsUtils.analytics.startSession() "Manual Session Started" } AnalyticsState.EndSession -> { - primaryAnalytics.endSession() - "Session Ended" - } - AnalyticsState.ShutDownAnalyticsSecondary -> { - secondaryAnalytics.shutdown() - "Rudder Analytics is shutting down. Init again if needed. This might take a second" - } - - AnalyticsState.TrackEventSecondary -> { - secondaryAnalytics.track( - eventName = "Track at ${Date()}", - trackProperties = TrackProperties("key1" to "prop1", "key2" to "prop2"), - options = RudderOptions.Builder().withIntegrations(mapOf("firebase" to false)) - .withExternalIds( - listOf(mapOf("fb_id" to "1234")) - ).build() - ) - "Track message sent" - } - - AnalyticsState.IdentifyEventSecondary -> { - secondaryAnalytics.identify( - userId = "some_user_id", traits = IdentifyTraits("trait1" to "some_trait") - ) - "Identify called" - } - - AnalyticsState.AliasEventSecondary -> { - secondaryAnalytics.alias(newId = "user_new_id") - "Alias called" - } - - AnalyticsState.GroupEventSecondary -> { - secondaryAnalytics.group( - groupId = "group_id", - groupTraits = GroupTraits("g_t1" to "t-1", "g_t2" to "t-2"), - ) - "Group called" - } - - AnalyticsState.ScreenEventSecondary -> { - secondaryAnalytics.screen( - screenName = "some_screen", - category = "some_category", - screenProperties = ScreenProperties() - ) - "Screen called" - } - - AnalyticsState.OptInAnalyticsSecondary -> { - secondaryAnalytics.optOut(primaryAnalytics.isOptedOut) - "OPT ${if (primaryAnalytics.isOptedOut) "out" else "in"} pressed" - } - - AnalyticsState.ForceFlushSecondary -> { - secondaryAnalytics.flush() - "Forcing a flush" - } - - AnalyticsState.EnableAutoTrackingSecondary -> { - secondaryAnalytics.applyConfigurationAndroid { - copy(trackAutoSession = true) - } - "Auto tracking enabled" - } - - AnalyticsState.DisableAutoTrackingSecondary -> { - secondaryAnalytics.applyConfigurationAndroid { - copy(trackAutoSession = false) - } - "Auto tracking disabled" - } - - AnalyticsState.StartManualSessionSecondary -> { - secondaryAnalytics.startSession() - "Manual Session Started" - } - - AnalyticsState.EndSessionSecondary -> { - secondaryAnalytics.endSession() + RudderAnalyticsUtils.analytics.endSession() "Session Ended" } AnalyticsState.SendError -> { @@ -232,7 +151,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { _rudderReporter?.errorClient?.notify(Exception("Non Fatal Exception")) "Sending an error" } - else -> "What's this?" } if (log.isNotEmpty()) addLogData(LogData(Date(), log)) } @@ -246,7 +164,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } override fun onCleared() { - primaryAnalytics.removePlugin(_loggingInterceptor) + analytics.removePlugin(_loggingInterceptor) super.onCleared() } } diff --git a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModelState.kt b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModelState.kt index 59beb070b..ad8263e44 100644 --- a/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModelState.kt +++ b/samples/sample-kotlin-android/src/main/kotlin/com/rudderstack/android/sampleapp/mainview/MainViewModelState.kt @@ -23,18 +23,5 @@ sealed class AnalyticsState(val eventName: String) { object EnableAutoTracking : AnalyticsState("Enable Auto Tracking") object DisableAutoTracking : AnalyticsState("Disable Auto Tracking") object ForceFlush : AnalyticsState("Force Flush") - - object ShutDownAnalyticsSecondary : AnalyticsState("ShutDown Secondary") - object TrackEventSecondary : AnalyticsState("Track Secondary") - object IdentifyEventSecondary : AnalyticsState("Identify Secondary") - object AliasEventSecondary : AnalyticsState("Alias Secondary") - object GroupEventSecondary : AnalyticsState("Group Secondary") - object ScreenEventSecondary : AnalyticsState("Screen Secondary") - object OptInAnalyticsSecondary : AnalyticsState("Opt In/Out Secondary") - object StartManualSessionSecondary : AnalyticsState("start manual session Secondary") - object EndSessionSecondary : AnalyticsState("End Session Secondary") - object EnableAutoTrackingSecondary : AnalyticsState("Enable Auto Tracking Secondary") - object DisableAutoTrackingSecondary : AnalyticsState("Disable Auto Tracking Secondary") - object ForceFlushSecondary : AnalyticsState("Force Flush Secondary") object SendError : AnalyticsState("Send Error") }