Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separation of API, State, and Side Effects #160

Merged
merged 8 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
100 changes: 16 additions & 84 deletions sdk/analytics/src/main/java/com/klaviyo/analytics/Klaviyo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import android.content.Intent
import com.klaviyo.analytics.model.Event
import com.klaviyo.analytics.model.EventKey
import com.klaviyo.analytics.model.EventMetric
import com.klaviyo.analytics.model.PROFILE_IDENTIFIERS
import com.klaviyo.analytics.model.Profile
import com.klaviyo.analytics.model.ProfileKey
import com.klaviyo.analytics.networking.ApiClient
import com.klaviyo.analytics.networking.KlaviyoApiClient
import com.klaviyo.analytics.state.KlaviyoState
import com.klaviyo.analytics.state.State
import com.klaviyo.analytics.state.StateSideEffects
import com.klaviyo.core.Registry
import com.klaviyo.core.config.Config
import com.klaviyo.core.config.LifecycleException
Expand All @@ -35,10 +37,8 @@ object Klaviyo {
)
val lifecycleCallbacks: ActivityLifecycleCallbacks get() = NoOpLifecycleCallbacks

private val profileOperationQueue = ProfileOperationQueue()

init {
// Since analytics platform owns ApiClient, we must register the service on initialize
// Since analytics module owns ApiClient, we must register the service on initialize
if (!Registry.isRegistered<ApiClient>()) Registry.register<ApiClient> { KlaviyoApiClient }
}

Expand All @@ -63,7 +63,8 @@ object Klaviyo {
registerActivityLifecycleCallbacks(Registry.lifecycleCallbacks)
} ?: throw LifecycleException()

UserInfo.startObservers()
Registry.register<State>(KlaviyoState())
Registry.register<StateSideEffects>(StateSideEffects())
}

/**
Expand All @@ -79,35 +80,7 @@ object Klaviyo {
* @return Returns [Klaviyo] for call chaining
*/
fun setProfile(profile: Profile): Klaviyo = safeApply {
if (UserInfo.isIdentified) {
// If a profile with external identifiers is already in state, we must reset.
// This conditional is important to preserve merging with an anonymous profile.
resetProfile()
}

// Copy the profile object, so we aren't mutating the argument
val mutableProfile = Profile().merge(profile)

// Route identifiers to the explicit setter functions to re-use that validator logic
mutableProfile.externalId?.let {
setExternalId(it)
mutableProfile.externalId = null
}

mutableProfile.email?.let {
setEmail(it)
mutableProfile.email = null
}

mutableProfile.phoneNumber?.let {
setPhoneNumber(it)
mutableProfile.phoneNumber = null
}

// Enqueue any remaining profile attributes
if (mutableProfile.propertyCount() > 0) {
profileOperationQueue.debounceProfileUpdate(mutableProfile)
}
Registry.get<State>().set(profile)
}

/**
Expand All @@ -131,7 +104,7 @@ object Klaviyo {
/**
* @return The email of the currently tracked profile, if set
*/
fun getEmail(): String? = safeCall { UserInfo.email.ifEmpty { null } }
fun getEmail(): String? = safeCall { Registry.get<State>().email }

/**
* Assigns a phone number to the currently tracked Klaviyo profile
Expand All @@ -158,9 +131,7 @@ object Klaviyo {
/**
* @return The phone number of the currently tracked profile, if set
*/
fun getPhoneNumber(): String? = safeCall {
UserInfo.phoneNumber.ifEmpty { null }
}
fun getPhoneNumber(): String? = safeCall { Registry.get<State>().phoneNumber }

/**
* Assigns a unique identifier to associate the currently tracked Klaviyo profile
Expand Down Expand Up @@ -188,9 +159,7 @@ object Klaviyo {
/**
* @return The external ID of the currently tracked profile, if set
*/
fun getExternalId(): String? = safeCall {
UserInfo.externalId.ifEmpty { null }
}
fun getExternalId(): String? = safeCall { Registry.get<State>().externalId }

/**
* Saves a push token and registers to the current profile
Expand All @@ -202,18 +171,12 @@ object Klaviyo {
*
* @param pushToken The push token provided by the device push service
*/
fun setPushToken(pushToken: String) = safeApply {
UserInfo.setPushToken(pushToken) {
Registry.get<ApiClient>().enqueuePushToken(pushToken, UserInfo.getAsProfile())
}
}
fun setPushToken(pushToken: String) = safeApply { Registry.get<State>().pushToken = pushToken }

/**
* @return The device push token, if one has been assigned to currently tracked profile
*/
fun getPushToken(): String? = safeCall {
UserInfo.pushToken.ifEmpty { null }
}
fun getPushToken(): String? = safeCall { Registry.get<State>().pushToken }

/**
* Assign an attribute to the currently tracked profile by key/value pair
Expand All @@ -229,32 +192,7 @@ object Klaviyo {
* @return Returns [Klaviyo] for call chaining
*/
fun setProfileAttribute(propertyKey: ProfileKey, value: String): Klaviyo = safeApply {
if (PROFILE_IDENTIFIERS.contains(propertyKey)) {
value.trim().ifEmpty {
Registry.log.warning(
"Empty string for $propertyKey will be ignored. To clear identifiers use resetProfile."
)
null
}?.also { validatedIdentifier ->
var property by when (propertyKey) {
ProfileKey.EXTERNAL_ID -> UserInfo::externalId
ProfileKey.EMAIL -> UserInfo::email
ProfileKey.PHONE_NUMBER -> UserInfo::phoneNumber
else -> return@safeApply
}

if (property != validatedIdentifier) {
property = validatedIdentifier
profileOperationQueue.debounceProfileUpdate(UserInfo.getAsProfile())
} else {
Registry.log.info(
"$propertyKey value was unchanged, the update will be ignored."
)
}
}
} else {
profileOperationQueue.debounceProfileUpdate(Profile(mapOf(propertyKey to value)))
}
Registry.get<State>().set(propertyKey, value)
}

/**
Expand All @@ -266,13 +204,7 @@ object Klaviyo {
* This should be called whenever an active user in your app is removed
* (e.g. after a logout)
*/
fun resetProfile() = safeApply {
// Flush any pending profile changes immediately
profileOperationQueue.flushProfile()

// Clear profile identifiers from state
UserInfo.reset()
}
fun resetProfile() = safeApply { Registry.get<State>().reset() }

/**
* Creates an [Event] associated with the currently tracked profile
Expand All @@ -281,8 +213,7 @@ object Klaviyo {
* @return Returns [Klaviyo] for call chaining
*/
fun createEvent(event: Event): Klaviyo = safeApply {
Registry.log.verbose("Enqueuing ${event.metric.name} event")
Registry.get<ApiClient>().enqueueEvent(event, UserInfo.getAsProfile())
Registry.get<ApiClient>().enqueueEvent(event, Registry.get<State>().get())
}

/**
Expand Down Expand Up @@ -327,6 +258,7 @@ object Klaviyo {
/**
* Checks whether a notification intent originated from Klaviyo
*/
@Suppress("MemberVisibilityCanBePrivate")
val Intent.isKlaviyoIntent: Boolean
get() = this.getStringExtra("com.klaviyo._k")?.isNotEmpty() ?: false
}

This file was deleted.

149 changes: 0 additions & 149 deletions sdk/analytics/src/main/java/com/klaviyo/analytics/UserInfo.kt

This file was deleted.

Loading
Loading