diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/DataplanBlockingUserTests.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/DataplanBlockingUserTests.kt index 84381e348..3af30432e 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/DataplanBlockingUserTests.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/DataplanBlockingUserTests.kt @@ -77,7 +77,7 @@ class DataplanBlockingUserTests : BaseKitOptionsTest() { assertTrue(allowedAttributes.containsKey(key)) assertFalse(blockedAttributes.containsKey(key)) } - attributeListenerKitKit.setUserAttribute = { key, _ -> + attributeListenerKitKit.setUserAttributeCallback = { key, _ -> assertTrue(allowedAttributes.containsKey(key)) assertFalse(blockedAttributes.containsKey(key)) } diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/AttributeListenerTestKit.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/AttributeListenerTestKit.kt index 2e6509808..1601264f1 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/AttributeListenerTestKit.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/AttributeListenerTestKit.kt @@ -10,7 +10,7 @@ open class AttributeListenerTestKit : ListenerTestKit(), AttributeListener, LogoutListener { - var setUserAttribute: ((attributeKey: String?, attributeValue: String?) -> Unit)? = null + var setUserAttributeCallback: ((attributeKey: String?, attributeValue: String?) -> Unit)? = null var setUserAttributeList: ((attributeKey: String?, attributeValueList: List?) -> Unit)? = null var supportsAttributeLists: (() -> Boolean)? = null @@ -41,14 +41,6 @@ open class AttributeListenerTestKit : userAttributeLists.forEach { onAttributeReceived?.invoke(it.key, it.value) } } - override fun setUserAttribute( - attributeKey: String, - attributeValue: String?, - ) { - setUserAttribute?.invoke(attributeKey, attributeValue) - onAttributeReceived?.invoke(attributeKey, attributeValue) - } - override fun setUserIdentity( identityType: MParticle.IdentityType, identity: String?, @@ -70,5 +62,17 @@ open class AttributeListenerTestKit : onAttributeReceived?.invoke(key, null) } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + setUserAttributeCallback?.invoke(key, value) + onAttributeReceived?.invoke(key, value) + } + override fun logout(): List = logout?.invoke() ?: listOf() } diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java index 2835b7a70..ef8541587 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java @@ -387,12 +387,19 @@ public interface BaseAttributeListener { * @param user filtered user context for this kit */ void onRemoveUserAttribute(String key, FilteredMParticleUser user); + + /** + * Called when a scalar user attribute is set for the current user. + * + * @param key attribute key + * @param value attribute value (may be non-String for some {@link UserAttributeListener} call paths) + * @param user filtered user context for this kit + */ + void onSetUserAttribute(String key, Object value, FilteredMParticleUser user); } public interface AttributeListener extends BaseAttributeListener { - void setUserAttribute(String attributeKey, String attributeValue); - void setUserAttributeList(String attributeKey, List attributeValueList); void setAllUserAttributes(Map userAttributes, Map> userAttributeLists); @@ -556,8 +563,6 @@ public interface UserAttributeListener extends BaseAttributeListener { void onIncrementUserAttribute(String key, Number incrementedBy, String value, FilteredMParticleUser user); - void onSetUserAttribute(String key, Object value, FilteredMParticleUser user); - void onSetUserTag(String key, FilteredMParticleUser user); void onSetUserAttributeList(String attributeKey, List attributeValueList, FilteredMParticleUser user); diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index 685ffddf6..c2b26a20c 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -685,34 +685,30 @@ private void setUserAttribute(KitIntegration provider, String attributeKey, List && !provider.isDisabled() && KitConfiguration.shouldForwardAttribute(provider.getConfiguration().getUserAttributeFilters(), attributeKey)) { boolean supportsAttributeLists = ((KitIntegration.BaseAttributeListener) provider).supportsAttributeLists(); + FilteredMParticleUser user = FilteredMParticleUser.getInstance(mpid, provider); if (provider instanceof KitIntegration.AttributeListener) { if (supportsAttributeLists) { ((KitIntegration.AttributeListener) provider).setUserAttributeList(attributeKey, valueList); } else { - ((KitIntegration.AttributeListener) provider).setUserAttribute(attributeKey, KitUtils.join(valueList)); + ((KitIntegration.BaseAttributeListener) provider).onSetUserAttribute(attributeKey, KitUtils.join(valueList), user); } } if (provider instanceof KitIntegration.UserAttributeListener) { if (supportsAttributeLists) { - ((KitIntegration.UserAttributeListener) provider).onSetUserAttributeList(attributeKey, valueList, FilteredMParticleUser.getInstance(mpid, provider)); + ((KitIntegration.UserAttributeListener) provider).onSetUserAttributeList(attributeKey, valueList, user); } else { - ((KitIntegration.UserAttributeListener) provider).onSetUserAttribute(attributeKey, KitUtils.join(valueList), FilteredMParticleUser.getInstance(mpid, provider)); + ((KitIntegration.UserAttributeListener) provider).onSetUserAttribute(attributeKey, KitUtils.join(valueList), user); } } } } private void setUserAttribute(KitIntegration provider, String attributeKey, String attributeValue, long mpid) { - if ((provider instanceof KitIntegration.AttributeListener || provider instanceof KitIntegration.UserAttributeListener) + if ((provider instanceof KitIntegration.BaseAttributeListener listener) && !provider.isDisabled() && KitConfiguration.shouldForwardAttribute(provider.getConfiguration().getUserAttributeFilters(), attributeKey)) { - if (provider instanceof KitIntegration.AttributeListener) { - ((KitIntegration.AttributeListener) provider).setUserAttribute(attributeKey, attributeValue); - } - if (provider instanceof KitIntegration.UserAttributeListener) { - ((KitIntegration.UserAttributeListener) provider).onSetUserAttribute(attributeKey, attributeValue, FilteredMParticleUser.getInstance(mpid, provider)); - } + listener.onSetUserAttribute(attributeKey, attributeValue, FilteredMParticleUser.getInstance(mpid, provider)); } } @@ -745,8 +741,8 @@ public void incrementUserAttribute(String key, Number incrementedBy, String newV if (provider instanceof KitIntegration.UserAttributeListener) { ((KitIntegration.UserAttributeListener) provider).onIncrementUserAttribute(key, incrementedBy, newValue, FilteredMParticleUser.getInstance(mpid, provider)); } - if (provider instanceof KitIntegration.AttributeListener) { - ((KitIntegration.AttributeListener) provider).setUserAttribute(key, newValue); + if (provider instanceof KitIntegration.BaseAttributeListener listener) { + listener.onSetUserAttribute(key, newValue, FilteredMParticleUser.getInstance(mpid, provider)); } } catch (Exception e) { Logger.warning("Failed to call onIncrementUserAttribute for kit: " + provider.getName() + ": " + e.getMessage()); diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index b4001ba63..4cd212028 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -23,6 +23,7 @@ import com.mparticle.internal.Logger import com.mparticle.internal.MPUtility import com.mparticle.internal.SideloadedKit import com.mparticle.kits.KitIntegration.AttributeListener +import com.mparticle.kits.KitIntegration.BaseAttributeListener import com.mparticle.mock.MockContext import com.mparticle.mock.MockKitConfiguration import com.mparticle.mock.MockKitManagerImpl @@ -49,6 +50,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.eq +import org.mockito.ArgumentMatchers.isNull import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.never @@ -557,8 +560,8 @@ class KitManagerImplTest { manager.setUserAttributeList("test key", attributeList, 1) verify(integration as AttributeListener, Mockito.times(1)) .setUserAttributeList("test key", attributeList) - verify(integration2 as AttributeListener, Mockito.times(1)) - .setUserAttribute("test key", "1,2,3") + verify(integration2 as BaseAttributeListener, Mockito.times(1)) + .onSetUserAttribute(eq("test key"), eq("1,2,3"), isNull()) } @Test diff --git a/kits/adobe/adobe-5/src/main/kotlin/com/mparticle/kits/AdobeKitBase.kt b/kits/adobe/adobe-5/src/main/kotlin/com/mparticle/kits/AdobeKitBase.kt index f66b5ae92..593c55cb5 100644 --- a/kits/adobe/adobe-5/src/main/kotlin/com/mparticle/kits/AdobeKitBase.kt +++ b/kits/adobe/adobe-5/src/main/kotlin/com/mparticle/kits/AdobeKitBase.kt @@ -48,13 +48,6 @@ abstract class AdobeKitBase : syncIds() } - override fun setUserAttribute( - s: String, - s1: String, - ) { - syncIds() - } - override fun setUserAttributeList( s: String, list: List, @@ -78,6 +71,17 @@ abstract class AdobeKitBase : syncIds() } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + syncIds() + } + override fun setUserIdentity( identityType: IdentityType, s: String, diff --git a/kits/adobemedia/adobemedia-5/src/main/kotlin/com/mparticle/kits/AdobeKit.kt b/kits/adobemedia/adobemedia-5/src/main/kotlin/com/mparticle/kits/AdobeKit.kt index 23baffa75..128a4822b 100644 --- a/kits/adobemedia/adobemedia-5/src/main/kotlin/com/mparticle/kits/AdobeKit.kt +++ b/kits/adobemedia/adobemedia-5/src/main/kotlin/com/mparticle/kits/AdobeKit.kt @@ -85,13 +85,6 @@ open class AdobeKit : syncIds() } - override fun setUserAttribute( - s: String, - s1: String, - ) { - syncIds() - } - override fun setUserAttributeList( s: String, list: List, @@ -115,6 +108,17 @@ open class AdobeKit : syncIds() } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + syncIds() + } + override fun setUserIdentity( identityType: MParticle.IdentityType, s: String, diff --git a/kits/appsflyer/appsflyer-6/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt b/kits/appsflyer/appsflyer-6/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt index 0dd18fa03..66adf2005 100644 --- a/kits/appsflyer/appsflyer-6/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt +++ b/kits/appsflyer/appsflyer-6/src/main/kotlin/com/mparticle/kits/AppsFlyerKit.kt @@ -248,11 +248,6 @@ class AppsFlyerKit : return messageList } - override fun setUserAttribute( - attributeKey: String, - attributeValue: String, - ) {} - override fun setUserAttributeList( s: String, list: List, diff --git a/kits/apptimize/apptimize-3/src/main/kotlin/com/mparticle/kits/ApptimizeKit.kt b/kits/apptimize/apptimize-3/src/main/kotlin/com/mparticle/kits/ApptimizeKit.kt index 6f8ff6b19..a784aab49 100644 --- a/kits/apptimize/apptimize-3/src/main/kotlin/com/mparticle/kits/ApptimizeKit.kt +++ b/kits/apptimize/apptimize-3/src/main/kotlin/com/mparticle/kits/ApptimizeKit.kt @@ -121,13 +121,6 @@ class ApptimizeKit : override fun getName(): String = KIT_NAME - override fun setUserAttribute( - key: String, - value: String, - ) { - Apptimize.setUserAttribute(key, value) - } - /** * Not supported by the Apptimize kit. */ @@ -148,7 +141,7 @@ class ApptimizeKit : attributeLists: Map>, ) { for ((key, value) in attributes) { - setUserAttribute(key, value) + Apptimize.setUserAttribute(key, value) } } @@ -159,6 +152,17 @@ class ApptimizeKit : Apptimize.clearUserAttribute(key) } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + Apptimize.setUserAttribute(key, value) + } + /** * @param identityType only Alias and CustomerId are suppoted by the Apptimize kit. */ diff --git a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt index 4432e6fc9..e138abf8d 100644 --- a/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt +++ b/kits/branch/branch-5/src/main/kotlin/com/mparticle/kits/BranchMetricsKit.kt @@ -174,11 +174,6 @@ class BranchMetricsKit : ) } - override fun setUserAttribute( - s: String, - s1: String, - ) {} - override fun setUserAttributeList( s: String, list: List, @@ -198,6 +193,14 @@ class BranchMetricsKit : // No-op: this kit does not implement this feature. } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + // No-op: this kit does not implement this feature. + } + override fun setUserIdentity( identityType: IdentityType, s: String, diff --git a/kits/braze/braze-38/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/kits/braze/braze-38/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 846812fd9..8eee4ffd4 100644 --- a/kits/braze/braze-38/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/kits/braze/braze-38/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -301,97 +301,126 @@ open class AppboyKit : return messages } - override fun setUserAttribute( + private fun applyScalarUserAttribute( keyIn: String, attributeValue: String, ) { - var key = keyIn Braze .getInstance(context) .getCurrentUser( object : IValueCallback { override fun onSuccess(value: BrazeUser) { val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - - when (key) { - UserAttributes.CITY -> value.setHomeCity(attributeValue) - UserAttributes.COUNTRY -> value.setCountry(attributeValue) - UserAttributes.FIRSTNAME -> value.setFirstName(attributeValue) - UserAttributes.LASTNAME -> value.setLastName(attributeValue) - UserAttributes.MOBILE_NUMBER -> value.setPhoneNumber(attributeValue) - UserAttributes.ZIPCODE -> value.setCustomUserAttribute("Zip", attributeValue) - UserAttributes.AGE -> { - val calendar = getCalendarMinusYears(attributeValue) - if (calendar != null) { - value.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) - } else { - Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + value) - } - } - EMAIL_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set email_subscribe with invalid value: " + value) - } - } - } - PUSH_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set push_subscribe with invalid value: " + value) - } - } - } - DOB -> useDobString(attributeValue, value) - UserAttributes.GENDER -> { - if (attributeValue.contains("fe")) { - value.setGender(Gender.FEMALE) - } else { - value.setGender(Gender.MALE) - } - } - else -> { - if (subscriptionGroupIds?.containsKey(key) == true) { - val groupId = subscriptionGroupIds?.get(key) - when (attributeValue.lowercase()) { - "true" -> { - groupId?.let { value.addToSubscriptionGroup(it) } - } - - "false" -> { - groupId?.let { value.removeFromSubscriptionGroup(it) } - } - - else -> { - Logger.warning( - "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", - ) - } - } - } else { - if (key.startsWith("$")) { - key = key.substring(1) - } - userAttributeSetter?.parseValue(key, attributeValue) - } - } - } + applyScalarUserAttributeOnUser(value, keyIn, attributeValue, userAttributeSetter) queueDataFlush() } override fun onError() { - Logger.warning("unable to set key: " + key + " with value: " + attributeValue) + Logger.warning("unable to set key: " + keyIn + " with value: " + attributeValue) } }, ) } + private fun applyScalarUserAttributeOnUser( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + when (key) { + UserAttributes.CITY -> user.setHomeCity(attributeValue) + UserAttributes.COUNTRY -> user.setCountry(attributeValue) + UserAttributes.FIRSTNAME -> user.setFirstName(attributeValue) + UserAttributes.LASTNAME -> user.setLastName(attributeValue) + UserAttributes.MOBILE_NUMBER -> user.setPhoneNumber(attributeValue) + UserAttributes.ZIPCODE -> user.setCustomUserAttribute("Zip", attributeValue) + UserAttributes.AGE -> setAgeAsDateOfBirthFromUserAttribute(user, attributeValue) + EMAIL_SUBSCRIBE -> setEmailSubscriptionFromUserAttribute(user, attributeValue) + PUSH_SUBSCRIBE -> setPushSubscriptionFromUserAttribute(user, attributeValue) + DOB -> useDobString(attributeValue, user) + UserAttributes.GENDER -> setGenderFromUserAttribute(user, attributeValue) + else -> applyUnmappedScalarUserAttribute(user, key, attributeValue, userAttributeSetter) + } + } + + private fun setAgeAsDateOfBirthFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + val calendar = getCalendarMinusYears(attributeValue) + if (calendar != null) { + user.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) + } else { + Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + attributeValue) + } + } + + private fun setEmailSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set email_subscribe with invalid value: $attributeValue") + } + } + + private fun setPushSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set push_subscribe with invalid value: $attributeValue") + } + } + + private fun setGenderFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + if (attributeValue.contains("fe")) { + user.setGender(Gender.FEMALE) + } else { + user.setGender(Gender.MALE) + } + } + + private fun applySubscriptionGroupAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + ) { + val groupId = subscriptionGroupIds?.get(key) + when (attributeValue.lowercase()) { + "true" -> groupId?.let { user.addToSubscriptionGroup(it) } + "false" -> groupId?.let { user.removeFromSubscriptionGroup(it) } + else -> + Logger.warning( + "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", + ) + } + } + + private fun applyUnmappedScalarUserAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + if (subscriptionGroupIds?.containsKey(key) == true) { + applySubscriptionGroupAttribute(user, key, attributeValue) + } else { + val customKey = if (key.startsWith("$")) key.substring(1) else key + userAttributeSetter.parseValue(customKey, attributeValue) + } + } + // Expected Date Format @"yyyy'-'MM'-'dd" private fun useDobString( value: String, @@ -482,6 +511,10 @@ open class AppboyKit : value: Any?, user: FilteredMParticleUser?, ) { + if (key == null || value == null || value !is String) { + return + } + applyScalarUserAttribute(key, value) } override fun onSetUserTag( @@ -650,7 +683,7 @@ open class AppboyKit : ) { if (!kitPreferences.getBoolean(PREF_KEY_HAS_SYNCED_ATTRIBUTES, false)) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyScalarUserAttribute(key, value) } for ((key, value) in attributeLists) { setUserAttributeList(key, value) diff --git a/kits/braze/braze-38/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt b/kits/braze/braze-38/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt index bbcf6419b..86c64652b 100644 --- a/kits/braze/braze-38/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt +++ b/kits/braze/braze-38/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt @@ -258,9 +258,10 @@ class AppboyKitTests { val currentUser = braze.currentUser kit.onKitCreate(settings, MockContextApplication()) - kit.setUserAttribute("test1", "true") - kit.setUserAttribute("test2", "false") - kit.setUserAttribute("test3", "notABoolean") + val filteredUser = Mockito.mock(FilteredMParticleUser::class.java) + kit.onSetUserAttribute("test1", "true", filteredUser) + kit.onSetUserAttribute("test2", "false", filteredUser) + kit.onSetUserAttribute("test3", "notABoolean", filteredUser) Assert.assertEquals(2, currentUser.getCustomUserAttribute().size.toLong()) } diff --git a/kits/braze/braze-39/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/kits/braze/braze-39/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 846812fd9..8eee4ffd4 100644 --- a/kits/braze/braze-39/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/kits/braze/braze-39/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -301,97 +301,126 @@ open class AppboyKit : return messages } - override fun setUserAttribute( + private fun applyScalarUserAttribute( keyIn: String, attributeValue: String, ) { - var key = keyIn Braze .getInstance(context) .getCurrentUser( object : IValueCallback { override fun onSuccess(value: BrazeUser) { val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - - when (key) { - UserAttributes.CITY -> value.setHomeCity(attributeValue) - UserAttributes.COUNTRY -> value.setCountry(attributeValue) - UserAttributes.FIRSTNAME -> value.setFirstName(attributeValue) - UserAttributes.LASTNAME -> value.setLastName(attributeValue) - UserAttributes.MOBILE_NUMBER -> value.setPhoneNumber(attributeValue) - UserAttributes.ZIPCODE -> value.setCustomUserAttribute("Zip", attributeValue) - UserAttributes.AGE -> { - val calendar = getCalendarMinusYears(attributeValue) - if (calendar != null) { - value.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) - } else { - Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + value) - } - } - EMAIL_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set email_subscribe with invalid value: " + value) - } - } - } - PUSH_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set push_subscribe with invalid value: " + value) - } - } - } - DOB -> useDobString(attributeValue, value) - UserAttributes.GENDER -> { - if (attributeValue.contains("fe")) { - value.setGender(Gender.FEMALE) - } else { - value.setGender(Gender.MALE) - } - } - else -> { - if (subscriptionGroupIds?.containsKey(key) == true) { - val groupId = subscriptionGroupIds?.get(key) - when (attributeValue.lowercase()) { - "true" -> { - groupId?.let { value.addToSubscriptionGroup(it) } - } - - "false" -> { - groupId?.let { value.removeFromSubscriptionGroup(it) } - } - - else -> { - Logger.warning( - "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", - ) - } - } - } else { - if (key.startsWith("$")) { - key = key.substring(1) - } - userAttributeSetter?.parseValue(key, attributeValue) - } - } - } + applyScalarUserAttributeOnUser(value, keyIn, attributeValue, userAttributeSetter) queueDataFlush() } override fun onError() { - Logger.warning("unable to set key: " + key + " with value: " + attributeValue) + Logger.warning("unable to set key: " + keyIn + " with value: " + attributeValue) } }, ) } + private fun applyScalarUserAttributeOnUser( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + when (key) { + UserAttributes.CITY -> user.setHomeCity(attributeValue) + UserAttributes.COUNTRY -> user.setCountry(attributeValue) + UserAttributes.FIRSTNAME -> user.setFirstName(attributeValue) + UserAttributes.LASTNAME -> user.setLastName(attributeValue) + UserAttributes.MOBILE_NUMBER -> user.setPhoneNumber(attributeValue) + UserAttributes.ZIPCODE -> user.setCustomUserAttribute("Zip", attributeValue) + UserAttributes.AGE -> setAgeAsDateOfBirthFromUserAttribute(user, attributeValue) + EMAIL_SUBSCRIBE -> setEmailSubscriptionFromUserAttribute(user, attributeValue) + PUSH_SUBSCRIBE -> setPushSubscriptionFromUserAttribute(user, attributeValue) + DOB -> useDobString(attributeValue, user) + UserAttributes.GENDER -> setGenderFromUserAttribute(user, attributeValue) + else -> applyUnmappedScalarUserAttribute(user, key, attributeValue, userAttributeSetter) + } + } + + private fun setAgeAsDateOfBirthFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + val calendar = getCalendarMinusYears(attributeValue) + if (calendar != null) { + user.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) + } else { + Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + attributeValue) + } + } + + private fun setEmailSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set email_subscribe with invalid value: $attributeValue") + } + } + + private fun setPushSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set push_subscribe with invalid value: $attributeValue") + } + } + + private fun setGenderFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + if (attributeValue.contains("fe")) { + user.setGender(Gender.FEMALE) + } else { + user.setGender(Gender.MALE) + } + } + + private fun applySubscriptionGroupAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + ) { + val groupId = subscriptionGroupIds?.get(key) + when (attributeValue.lowercase()) { + "true" -> groupId?.let { user.addToSubscriptionGroup(it) } + "false" -> groupId?.let { user.removeFromSubscriptionGroup(it) } + else -> + Logger.warning( + "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", + ) + } + } + + private fun applyUnmappedScalarUserAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + if (subscriptionGroupIds?.containsKey(key) == true) { + applySubscriptionGroupAttribute(user, key, attributeValue) + } else { + val customKey = if (key.startsWith("$")) key.substring(1) else key + userAttributeSetter.parseValue(customKey, attributeValue) + } + } + // Expected Date Format @"yyyy'-'MM'-'dd" private fun useDobString( value: String, @@ -482,6 +511,10 @@ open class AppboyKit : value: Any?, user: FilteredMParticleUser?, ) { + if (key == null || value == null || value !is String) { + return + } + applyScalarUserAttribute(key, value) } override fun onSetUserTag( @@ -650,7 +683,7 @@ open class AppboyKit : ) { if (!kitPreferences.getBoolean(PREF_KEY_HAS_SYNCED_ATTRIBUTES, false)) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyScalarUserAttribute(key, value) } for ((key, value) in attributeLists) { setUserAttributeList(key, value) diff --git a/kits/braze/braze-39/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt b/kits/braze/braze-39/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt index bbcf6419b..86c64652b 100644 --- a/kits/braze/braze-39/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt +++ b/kits/braze/braze-39/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt @@ -258,9 +258,10 @@ class AppboyKitTests { val currentUser = braze.currentUser kit.onKitCreate(settings, MockContextApplication()) - kit.setUserAttribute("test1", "true") - kit.setUserAttribute("test2", "false") - kit.setUserAttribute("test3", "notABoolean") + val filteredUser = Mockito.mock(FilteredMParticleUser::class.java) + kit.onSetUserAttribute("test1", "true", filteredUser) + kit.onSetUserAttribute("test2", "false", filteredUser) + kit.onSetUserAttribute("test3", "notABoolean", filteredUser) Assert.assertEquals(2, currentUser.getCustomUserAttribute().size.toLong()) } diff --git a/kits/braze/braze-40/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/kits/braze/braze-40/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 846812fd9..8eee4ffd4 100644 --- a/kits/braze/braze-40/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/kits/braze/braze-40/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -301,97 +301,126 @@ open class AppboyKit : return messages } - override fun setUserAttribute( + private fun applyScalarUserAttribute( keyIn: String, attributeValue: String, ) { - var key = keyIn Braze .getInstance(context) .getCurrentUser( object : IValueCallback { override fun onSuccess(value: BrazeUser) { val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - - when (key) { - UserAttributes.CITY -> value.setHomeCity(attributeValue) - UserAttributes.COUNTRY -> value.setCountry(attributeValue) - UserAttributes.FIRSTNAME -> value.setFirstName(attributeValue) - UserAttributes.LASTNAME -> value.setLastName(attributeValue) - UserAttributes.MOBILE_NUMBER -> value.setPhoneNumber(attributeValue) - UserAttributes.ZIPCODE -> value.setCustomUserAttribute("Zip", attributeValue) - UserAttributes.AGE -> { - val calendar = getCalendarMinusYears(attributeValue) - if (calendar != null) { - value.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) - } else { - Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + value) - } - } - EMAIL_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set email_subscribe with invalid value: " + value) - } - } - } - PUSH_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set push_subscribe with invalid value: " + value) - } - } - } - DOB -> useDobString(attributeValue, value) - UserAttributes.GENDER -> { - if (attributeValue.contains("fe")) { - value.setGender(Gender.FEMALE) - } else { - value.setGender(Gender.MALE) - } - } - else -> { - if (subscriptionGroupIds?.containsKey(key) == true) { - val groupId = subscriptionGroupIds?.get(key) - when (attributeValue.lowercase()) { - "true" -> { - groupId?.let { value.addToSubscriptionGroup(it) } - } - - "false" -> { - groupId?.let { value.removeFromSubscriptionGroup(it) } - } - - else -> { - Logger.warning( - "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", - ) - } - } - } else { - if (key.startsWith("$")) { - key = key.substring(1) - } - userAttributeSetter?.parseValue(key, attributeValue) - } - } - } + applyScalarUserAttributeOnUser(value, keyIn, attributeValue, userAttributeSetter) queueDataFlush() } override fun onError() { - Logger.warning("unable to set key: " + key + " with value: " + attributeValue) + Logger.warning("unable to set key: " + keyIn + " with value: " + attributeValue) } }, ) } + private fun applyScalarUserAttributeOnUser( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + when (key) { + UserAttributes.CITY -> user.setHomeCity(attributeValue) + UserAttributes.COUNTRY -> user.setCountry(attributeValue) + UserAttributes.FIRSTNAME -> user.setFirstName(attributeValue) + UserAttributes.LASTNAME -> user.setLastName(attributeValue) + UserAttributes.MOBILE_NUMBER -> user.setPhoneNumber(attributeValue) + UserAttributes.ZIPCODE -> user.setCustomUserAttribute("Zip", attributeValue) + UserAttributes.AGE -> setAgeAsDateOfBirthFromUserAttribute(user, attributeValue) + EMAIL_SUBSCRIBE -> setEmailSubscriptionFromUserAttribute(user, attributeValue) + PUSH_SUBSCRIBE -> setPushSubscriptionFromUserAttribute(user, attributeValue) + DOB -> useDobString(attributeValue, user) + UserAttributes.GENDER -> setGenderFromUserAttribute(user, attributeValue) + else -> applyUnmappedScalarUserAttribute(user, key, attributeValue, userAttributeSetter) + } + } + + private fun setAgeAsDateOfBirthFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + val calendar = getCalendarMinusYears(attributeValue) + if (calendar != null) { + user.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) + } else { + Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + attributeValue) + } + } + + private fun setEmailSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set email_subscribe with invalid value: $attributeValue") + } + } + + private fun setPushSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set push_subscribe with invalid value: $attributeValue") + } + } + + private fun setGenderFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + if (attributeValue.contains("fe")) { + user.setGender(Gender.FEMALE) + } else { + user.setGender(Gender.MALE) + } + } + + private fun applySubscriptionGroupAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + ) { + val groupId = subscriptionGroupIds?.get(key) + when (attributeValue.lowercase()) { + "true" -> groupId?.let { user.addToSubscriptionGroup(it) } + "false" -> groupId?.let { user.removeFromSubscriptionGroup(it) } + else -> + Logger.warning( + "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", + ) + } + } + + private fun applyUnmappedScalarUserAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + if (subscriptionGroupIds?.containsKey(key) == true) { + applySubscriptionGroupAttribute(user, key, attributeValue) + } else { + val customKey = if (key.startsWith("$")) key.substring(1) else key + userAttributeSetter.parseValue(customKey, attributeValue) + } + } + // Expected Date Format @"yyyy'-'MM'-'dd" private fun useDobString( value: String, @@ -482,6 +511,10 @@ open class AppboyKit : value: Any?, user: FilteredMParticleUser?, ) { + if (key == null || value == null || value !is String) { + return + } + applyScalarUserAttribute(key, value) } override fun onSetUserTag( @@ -650,7 +683,7 @@ open class AppboyKit : ) { if (!kitPreferences.getBoolean(PREF_KEY_HAS_SYNCED_ATTRIBUTES, false)) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyScalarUserAttribute(key, value) } for ((key, value) in attributeLists) { setUserAttributeList(key, value) diff --git a/kits/braze/braze-40/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt b/kits/braze/braze-40/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt index bbcf6419b..86c64652b 100644 --- a/kits/braze/braze-40/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt +++ b/kits/braze/braze-40/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt @@ -258,9 +258,10 @@ class AppboyKitTests { val currentUser = braze.currentUser kit.onKitCreate(settings, MockContextApplication()) - kit.setUserAttribute("test1", "true") - kit.setUserAttribute("test2", "false") - kit.setUserAttribute("test3", "notABoolean") + val filteredUser = Mockito.mock(FilteredMParticleUser::class.java) + kit.onSetUserAttribute("test1", "true", filteredUser) + kit.onSetUserAttribute("test2", "false", filteredUser) + kit.onSetUserAttribute("test3", "notABoolean", filteredUser) Assert.assertEquals(2, currentUser.getCustomUserAttribute().size.toLong()) } diff --git a/kits/braze/braze-41/src/main/kotlin/com/mparticle/kits/AppboyKit.kt b/kits/braze/braze-41/src/main/kotlin/com/mparticle/kits/AppboyKit.kt index 846812fd9..8eee4ffd4 100644 --- a/kits/braze/braze-41/src/main/kotlin/com/mparticle/kits/AppboyKit.kt +++ b/kits/braze/braze-41/src/main/kotlin/com/mparticle/kits/AppboyKit.kt @@ -301,97 +301,126 @@ open class AppboyKit : return messages } - override fun setUserAttribute( + private fun applyScalarUserAttribute( keyIn: String, attributeValue: String, ) { - var key = keyIn Braze .getInstance(context) .getCurrentUser( object : IValueCallback { override fun onSuccess(value: BrazeUser) { val userAttributeSetter = UserAttributeSetter(value, enableTypeDetection) - - when (key) { - UserAttributes.CITY -> value.setHomeCity(attributeValue) - UserAttributes.COUNTRY -> value.setCountry(attributeValue) - UserAttributes.FIRSTNAME -> value.setFirstName(attributeValue) - UserAttributes.LASTNAME -> value.setLastName(attributeValue) - UserAttributes.MOBILE_NUMBER -> value.setPhoneNumber(attributeValue) - UserAttributes.ZIPCODE -> value.setCustomUserAttribute("Zip", attributeValue) - UserAttributes.AGE -> { - val calendar = getCalendarMinusYears(attributeValue) - if (calendar != null) { - value.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) - } else { - Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + value) - } - } - EMAIL_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set email_subscribe with invalid value: " + value) - } - } - } - PUSH_SUBSCRIBE -> { - when (attributeValue) { - OPTED_IN -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) - UNSUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) - SUBSCRIBED -> value.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) - else -> { - Logger.warning("unable to set push_subscribe with invalid value: " + value) - } - } - } - DOB -> useDobString(attributeValue, value) - UserAttributes.GENDER -> { - if (attributeValue.contains("fe")) { - value.setGender(Gender.FEMALE) - } else { - value.setGender(Gender.MALE) - } - } - else -> { - if (subscriptionGroupIds?.containsKey(key) == true) { - val groupId = subscriptionGroupIds?.get(key) - when (attributeValue.lowercase()) { - "true" -> { - groupId?.let { value.addToSubscriptionGroup(it) } - } - - "false" -> { - groupId?.let { value.removeFromSubscriptionGroup(it) } - } - - else -> { - Logger.warning( - "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", - ) - } - } - } else { - if (key.startsWith("$")) { - key = key.substring(1) - } - userAttributeSetter?.parseValue(key, attributeValue) - } - } - } + applyScalarUserAttributeOnUser(value, keyIn, attributeValue, userAttributeSetter) queueDataFlush() } override fun onError() { - Logger.warning("unable to set key: " + key + " with value: " + attributeValue) + Logger.warning("unable to set key: " + keyIn + " with value: " + attributeValue) } }, ) } + private fun applyScalarUserAttributeOnUser( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + when (key) { + UserAttributes.CITY -> user.setHomeCity(attributeValue) + UserAttributes.COUNTRY -> user.setCountry(attributeValue) + UserAttributes.FIRSTNAME -> user.setFirstName(attributeValue) + UserAttributes.LASTNAME -> user.setLastName(attributeValue) + UserAttributes.MOBILE_NUMBER -> user.setPhoneNumber(attributeValue) + UserAttributes.ZIPCODE -> user.setCustomUserAttribute("Zip", attributeValue) + UserAttributes.AGE -> setAgeAsDateOfBirthFromUserAttribute(user, attributeValue) + EMAIL_SUBSCRIBE -> setEmailSubscriptionFromUserAttribute(user, attributeValue) + PUSH_SUBSCRIBE -> setPushSubscriptionFromUserAttribute(user, attributeValue) + DOB -> useDobString(attributeValue, user) + UserAttributes.GENDER -> setGenderFromUserAttribute(user, attributeValue) + else -> applyUnmappedScalarUserAttribute(user, key, attributeValue, userAttributeSetter) + } + } + + private fun setAgeAsDateOfBirthFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + val calendar = getCalendarMinusYears(attributeValue) + if (calendar != null) { + user.setDateOfBirth(calendar[Calendar.YEAR], Month.JANUARY, 1) + } else { + Logger.warning("unable to set DateOfBirth for " + UserAttributes.AGE + " = " + attributeValue) + } + } + + private fun setEmailSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setEmailNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set email_subscribe with invalid value: $attributeValue") + } + } + + private fun setPushSubscriptionFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + when (attributeValue) { + OPTED_IN -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.OPTED_IN) + UNSUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.UNSUBSCRIBED) + SUBSCRIBED -> user.setPushNotificationSubscriptionType(NotificationSubscriptionType.SUBSCRIBED) + else -> Logger.warning("unable to set push_subscribe with invalid value: $attributeValue") + } + } + + private fun setGenderFromUserAttribute( + user: BrazeUser, + attributeValue: String, + ) { + if (attributeValue.contains("fe")) { + user.setGender(Gender.FEMALE) + } else { + user.setGender(Gender.MALE) + } + } + + private fun applySubscriptionGroupAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + ) { + val groupId = subscriptionGroupIds?.get(key) + when (attributeValue.lowercase()) { + "true" -> groupId?.let { user.addToSubscriptionGroup(it) } + "false" -> groupId?.let { user.removeFromSubscriptionGroup(it) } + else -> + Logger.warning( + "Unable to set Subscription Group ID for user attribute: $key due to invalid value data type. Expected Boolean.", + ) + } + } + + private fun applyUnmappedScalarUserAttribute( + user: BrazeUser, + key: String, + attributeValue: String, + userAttributeSetter: UserAttributeSetter, + ) { + if (subscriptionGroupIds?.containsKey(key) == true) { + applySubscriptionGroupAttribute(user, key, attributeValue) + } else { + val customKey = if (key.startsWith("$")) key.substring(1) else key + userAttributeSetter.parseValue(customKey, attributeValue) + } + } + // Expected Date Format @"yyyy'-'MM'-'dd" private fun useDobString( value: String, @@ -482,6 +511,10 @@ open class AppboyKit : value: Any?, user: FilteredMParticleUser?, ) { + if (key == null || value == null || value !is String) { + return + } + applyScalarUserAttribute(key, value) } override fun onSetUserTag( @@ -650,7 +683,7 @@ open class AppboyKit : ) { if (!kitPreferences.getBoolean(PREF_KEY_HAS_SYNCED_ATTRIBUTES, false)) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyScalarUserAttribute(key, value) } for ((key, value) in attributeLists) { setUserAttributeList(key, value) diff --git a/kits/braze/braze-41/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt b/kits/braze/braze-41/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt index bbcf6419b..86c64652b 100644 --- a/kits/braze/braze-41/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt +++ b/kits/braze/braze-41/src/test/kotlin/com/mparticle/kits/AppboyKitTests.kt @@ -258,9 +258,10 @@ class AppboyKitTests { val currentUser = braze.currentUser kit.onKitCreate(settings, MockContextApplication()) - kit.setUserAttribute("test1", "true") - kit.setUserAttribute("test2", "false") - kit.setUserAttribute("test3", "notABoolean") + val filteredUser = Mockito.mock(FilteredMParticleUser::class.java) + kit.onSetUserAttribute("test1", "true", filteredUser) + kit.onSetUserAttribute("test2", "false", filteredUser) + kit.onSetUserAttribute("test3", "notABoolean", filteredUser) Assert.assertEquals(2, currentUser.getCustomUserAttribute().size.toLong()) } diff --git a/kits/clevertap/clevertap-7/src/main/kotlin/com/mparticle/kits/CleverTapKit.kt b/kits/clevertap/clevertap-7/src/main/kotlin/com/mparticle/kits/CleverTapKit.kt index 0b84adf9a..dae98621c 100644 --- a/kits/clevertap/clevertap-7/src/main/kotlin/com/mparticle/kits/CleverTapKit.kt +++ b/kits/clevertap/clevertap-7/src/main/kotlin/com/mparticle/kits/CleverTapKit.kt @@ -237,40 +237,43 @@ class CleverTapKit : } override fun onSetUserAttribute( - keyIn: String, - valueIn: Any, - user: FilteredMParticleUser, + key: String?, + value: Any?, + user: FilteredMParticleUser?, ) { - var key = keyIn - var value = valueIn + if (key == null || value == null) { + return + } + var mappedKey = key + var mappedValue: Any = value val profile = HashMap() when { - BIRTHDAY == key -> { - key = DOB + BIRTHDAY == mappedKey -> { + mappedKey = DOB } - "name" == key -> { - key = NAME + "name" == mappedKey -> { + mappedKey = NAME } - UserAttributes.GENDER == key -> { - val genderValue = value as String - value = + UserAttributes.GENDER == mappedKey -> { + val genderValue = mappedValue as String + mappedValue = if (genderValue.contains("fe")) { FEMALE } else { MALE } } - UserAttributes.MOBILE_NUMBER == key -> { - key = PHONE + UserAttributes.MOBILE_NUMBER == mappedKey -> { + mappedKey = PHONE } else -> { - if (key.startsWith("$")) { - key = key.substring(1) + if (mappedKey.startsWith("$")) { + mappedKey = mappedKey.substring(1) } } } - profile[key] = value + profile[mappedKey] = mappedValue cleverTapInstance?.pushProfile(profile) } diff --git a/kits/comscore/comscore-6/src/main/kotlin/com/mparticle/kits/ComscoreKit.kt b/kits/comscore/comscore-6/src/main/kotlin/com/mparticle/kits/ComscoreKit.kt index 4f2f8ce61..c0792c427 100644 --- a/kits/comscore/comscore-6/src/main/kotlin/com/mparticle/kits/ComscoreKit.kt +++ b/kits/comscore/comscore-6/src/main/kotlin/com/mparticle/kits/ComscoreKit.kt @@ -83,7 +83,7 @@ class ComscoreKit : .build(), )!! - override fun setUserAttribute( + private fun applyEnterpriseScalarUserAttribute( key: String, value: String, ) { @@ -107,7 +107,7 @@ class ComscoreKit : ) { if (isEnterprise) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyEnterpriseScalarUserAttribute(key, value) } } } @@ -121,6 +121,17 @@ class ComscoreKit : } } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + applyEnterpriseScalarUserAttribute(key, value) + } + override fun removeUserIdentity(identityType: IdentityType) { if (isEnterprise) { Analytics.getConfiguration().removePersistentLabel(identityType.toString()) diff --git a/kits/ga/ga-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseKit.kt b/kits/ga/ga-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseKit.kt index c1e8293c2..a41e46c6a 100644 --- a/kits/ga/ga-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseKit.kt +++ b/kits/ga/ga-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseKit.kt @@ -400,10 +400,13 @@ class GoogleAnalyticsFirebaseKit : * We are going to ignore Lists here, since Firebase only supports String "user property" values */ override fun onSetUserAttribute( - key: String, - value: Any, - filteredMParticleUser: FilteredMParticleUser, + key: String?, + value: Any?, + user: FilteredMParticleUser?, ) { + if (key == null) { + return + } if (value is String) { standardizeName(key, false)?.let { FirebaseAnalytics.getInstance(context).setUserProperty( diff --git a/kits/ga4/ga4-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseGA4Kit.kt b/kits/ga4/ga4-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseGA4Kit.kt index d8c0e31d3..1a75cfa65 100644 --- a/kits/ga4/ga4-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseGA4Kit.kt +++ b/kits/ga4/ga4-23/src/main/kotlin/com/mparticle/kits/GoogleAnalyticsFirebaseGA4Kit.kt @@ -611,10 +611,13 @@ class GoogleAnalyticsFirebaseGA4Kit : * We are going to ignore Lists here, since Firebase only supports String "user property" values */ override fun onSetUserAttribute( - key: String, - value: Any, - filteredMParticleUser: FilteredMParticleUser, + key: String?, + value: Any?, + user: FilteredMParticleUser?, ) { + if (key == null) { + return + } if (value is String) { standardizeName(key, false)?.let { FirebaseAnalytics.getInstance(context).setUserProperty( diff --git a/kits/kochava/kochava-5/src/main/kotlin/com/mparticle/kits/KochavaKit.kt b/kits/kochava/kochava-5/src/main/kotlin/com/mparticle/kits/KochavaKit.kt index eca6a862e..b3fdf46df 100644 --- a/kits/kochava/kochava-5/src/main/kotlin/com/mparticle/kits/KochavaKit.kt +++ b/kits/kochava/kochava-5/src/main/kotlin/com/mparticle/kits/KochavaKit.kt @@ -82,11 +82,6 @@ class KochavaKit : // No-op: this kit does not implement this feature. } - override fun setUserAttribute( - attributeKey: String, - attributeValue: String, - ) {} - override fun setUserAttributeList( s: String, list: List, @@ -106,6 +101,14 @@ class KochavaKit : // No-op: this kit does not implement this feature. } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + // No-op: this kit does not implement this feature. + } + override fun setInstallReferrer(intent: Intent) {} override fun setUserIdentity( diff --git a/kits/leanplum/leanplum-7/src/main/kotlin/com/mparticle/kits/LeanplumKit.kt b/kits/leanplum/leanplum-7/src/main/kotlin/com/mparticle/kits/LeanplumKit.kt index fd378d05e..39ed8bbfd 100644 --- a/kits/leanplum/leanplum-7/src/main/kotlin/com/mparticle/kits/LeanplumKit.kt +++ b/kits/leanplum/leanplum-7/src/main/kotlin/com/mparticle/kits/LeanplumKit.kt @@ -214,10 +214,13 @@ class LeanplumKit : override fun setOptOut(optedOut: Boolean): List = emptyList() override fun onSetUserAttribute( - key: String, - value: Any, - user: FilteredMParticleUser, + key: String?, + value: Any?, + user: FilteredMParticleUser?, ) { + if (key == null) { + return + } val attributes = mutableMapOf() attributes[key] = value setAttributesAndCheckId(attributes) diff --git a/kits/localytics/localytics-6/src/main/kotlin/com/mparticle/kits/LocalyticsKit.kt b/kits/localytics/localytics-6/src/main/kotlin/com/mparticle/kits/LocalyticsKit.kt index d7587d85b..8d7070b7f 100644 --- a/kits/localytics/localytics-6/src/main/kotlin/com/mparticle/kits/LocalyticsKit.kt +++ b/kits/localytics/localytics-6/src/main/kotlin/com/mparticle/kits/LocalyticsKit.kt @@ -127,7 +127,7 @@ class LocalyticsKit : return -1 } - override fun setUserAttribute( + private fun applyScalarUserAttribute( key: String, value: String, ) { @@ -163,7 +163,7 @@ class LocalyticsKit : attributeLists: Map>, ) { for ((key, value) in attributes) { - setUserAttribute(key, value) + applyScalarUserAttribute(key, value) } for ((key, value) in attributeLists) { setUserAttributeList(key, value) @@ -177,6 +177,17 @@ class LocalyticsKit : Localytics.deleteProfileAttribute(key) } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + applyScalarUserAttribute(key, value) + } + override fun setOptOut(optOutStatus: Boolean): List { Localytics.setOptedOut(optOutStatus) val messageList: MutableList = LinkedList() diff --git a/kits/singular/singular-12/src/main/kotlin/com/mparticle/kits/SingularKit.kt b/kits/singular/singular-12/src/main/kotlin/com/mparticle/kits/SingularKit.kt index 8608b07ff..1c11a302a 100644 --- a/kits/singular/singular-12/src/main/kotlin/com/mparticle/kits/SingularKit.kt +++ b/kits/singular/singular-12/src/main/kotlin/com/mparticle/kits/SingularKit.kt @@ -340,32 +340,6 @@ open class SingularKit : //endregion //endregion //region Deprecated Attribute Listener - override fun setUserAttribute( - key: String, - value: String, - ) { - // TODO: Debug these lines to understand the code - val map = HashMap() - if (MParticle.UserAttributes.AGE == key) { - map[USER_AGE_KEY] = value - } else if (MParticle.UserAttributes.GENDER == key) { - if (value.contains("fe")) { - map[USER_GENDER_KEY] = "f" - } else { - map[USER_GENDER_KEY] = "m" - } - } - if (map.isNotEmpty()) { - executeIfSingularInitialized( - { - Singular.eventJSON("UserAttribute", (map as Map<*, *>?)?.let { JSONObject(it) }) - }, - forceInitSingular = false, - "setUserAttribute", - ) - } - } - override fun setUserAttributeList( s: String, list: List, @@ -385,10 +359,32 @@ open class SingularKit : ) {} override fun onSetUserAttribute( - s: String, - o: Any, - filteredMParticleUser: FilteredMParticleUser, + key: String?, + value: Any?, + user: FilteredMParticleUser?, ) { + if (key == null || value == null || value !is String) { + return + } + val map = HashMap() + if (MParticle.UserAttributes.AGE == key) { + map[USER_AGE_KEY] = value + } else if (MParticle.UserAttributes.GENDER == key) { + if (value.contains("fe")) { + map[USER_GENDER_KEY] = "f" + } else { + map[USER_GENDER_KEY] = "m" + } + } + if (map.isNotEmpty()) { + executeIfSingularInitialized( + { + Singular.eventJSON("UserAttribute", (map as Map<*, *>?)?.let { JSONObject(it) }) + }, + forceInitSingular = false, + "setUserAttribute", + ) + } } override fun onSetUserTag( diff --git a/kits/urbanairship/urbanairship-20/src/main/kotlin/com/mparticle/kits/UrbanAirshipKit.kt b/kits/urbanairship/urbanairship-20/src/main/kotlin/com/mparticle/kits/UrbanAirshipKit.kt index 7063ef4a5..0b34eb8fe 100644 --- a/kits/urbanairship/urbanairship-20/src/main/kotlin/com/mparticle/kits/UrbanAirshipKit.kt +++ b/kits/urbanairship/urbanairship-20/src/main/kotlin/com/mparticle/kits/UrbanAirshipKit.kt @@ -237,25 +237,6 @@ class UrbanAirshipKit : } } - override fun setUserAttribute( - key: String, - value: String, - ) { - if (configuration?.enableTags == true) { - if (KitUtils.isEmpty(value)) { - Airship.channel - .editTags() - .addTag(KitUtils.sanitizeAttributeKey(key)) - .apply() - } else if (configuration?.includeUserAttributes == true) { - Airship.channel - .editTags() - .addTag(KitUtils.sanitizeAttributeKey(key) + "-" + value) - .apply() - } - } - } - override fun setUserAttributeList( s: String, list: List, @@ -294,6 +275,29 @@ class UrbanAirshipKit : .apply() } + override fun onSetUserAttribute( + key: String?, + value: Any?, + user: FilteredMParticleUser?, + ) { + if (key == null || value == null || value !is String) { + return + } + if (configuration?.enableTags == true) { + if (KitUtils.isEmpty(value)) { + Airship.channel + .editTags() + .addTag(KitUtils.sanitizeAttributeKey(key)) + .apply() + } else if (configuration?.includeUserAttributes == true) { + Airship.channel + .editTags() + .addTag(KitUtils.sanitizeAttributeKey(key) + "-" + value) + .apply() + } + } + } + // not supported override fun logout(): List = emptyList()