Skip to content

Commit

Permalink
feat: support for custom device attributes and config (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
Shahroz16 committed Mar 24, 2022
1 parent 4ec4374 commit a7dbaba
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 30 deletions.
9 changes: 8 additions & 1 deletion app/src/main/java/io/customer/example/MainActivity.kt
Expand Up @@ -28,10 +28,17 @@ class MainActivity : AppCompatActivity() {
// log events
// makeEventsRequests()

// add custom attributes
makeAddCustomDeviceAttributesRequest()

// register device
makeRegisterDeviceRequest()
}

private fun makeAddCustomDeviceAttributesRequest() {
CustomerIO.instance().deviceAttributes.putAll(mapOf("bingo" to "heyaa"))
}

private fun makeRegisterDeviceRequest() {
CustomerIO.instance().registerDeviceToken("token").enqueue(outputCallback)
}
Expand Down Expand Up @@ -99,7 +106,7 @@ class MainActivity : AppCompatActivity() {
when (
val result =
CustomerIO.instance().identify(
identifier = "support-ticket-test",
identifier = "device-attri",
mapOf("created_at" to 1642659790)
).execute()
) {
Expand Down
25 changes: 23 additions & 2 deletions sdk/src/main/java/io/customer/sdk/CustomerIO.kt
Expand Up @@ -48,6 +48,7 @@ class CustomerIO internal constructor(
private var timeout = 6000L
private var urlHandler: CustomerIOUrlHandler? = null
private var shouldAutoRecordScreenViews: Boolean = false
private var autoTrackDeviceAttributes: Boolean = true

private lateinit var activityLifecycleCallback: CustomerIOActivityLifecycleCallbacks

Expand All @@ -66,6 +67,11 @@ class CustomerIO internal constructor(
return this
}

fun autoTrackDeviceAttributes(shouldTrackDeviceAttributes: Boolean): Builder {
this.autoTrackDeviceAttributes = shouldTrackDeviceAttributes
return this
}

/**
* Override url/deep link handling
*
Expand All @@ -92,7 +98,8 @@ class CustomerIO internal constructor(
region = region,
timeout = timeout,
urlHandler = urlHandler,
autoTrackScreenViews = shouldAutoRecordScreenViews
autoTrackScreenViews = shouldAutoRecordScreenViews,
autoTrackDeviceAttributes = autoTrackDeviceAttributes,
)

val customerIoComponent =
Expand Down Expand Up @@ -178,7 +185,15 @@ class CustomerIO internal constructor(
* is no active customer, this will fail to register the device
*/
fun registerDeviceToken(deviceToken: String): Action<Unit> =
api.registerDeviceToken(deviceToken, store.deviceStore.buildDeviceAttributes())
api.registerDeviceToken(deviceToken, collectDeviceAttributes())

private fun collectDeviceAttributes(): Map<String, Any> {
return if (config.autoTrackDeviceAttributes) {
deviceAttributes + store.deviceStore.buildDeviceAttributes()
} else {
deviceAttributes
}
}

/**
* Delete the currently registered device token
Expand All @@ -198,6 +213,12 @@ class CustomerIO internal constructor(
deviceToken = deviceToken
)

/**
* Use to provide additional and custom device attributes
* apart from the ones the SDK is programmed to send to customer workspace.
*/
val deviceAttributes: MutableMap<String, Any> = mutableMapOf()

private fun recordScreenViews(activity: Activity, attributes: Map<String, Any>): Action<Unit> {
val packageManager = activity.packageManager
return try {
Expand Down
5 changes: 3 additions & 2 deletions sdk/src/main/java/io/customer/sdk/CustomerIOConfig.kt
Expand Up @@ -3,11 +3,12 @@ package io.customer.sdk
import io.customer.sdk.data.communication.CustomerIOUrlHandler
import io.customer.sdk.data.model.Region

class CustomerIOConfig(
data class CustomerIOConfig(
val siteId: String,
val apiKey: String,
val region: Region,
val timeout: Long,
val urlHandler: CustomerIOUrlHandler?,
val autoTrackScreenViews: Boolean
val autoTrackScreenViews: Boolean,
val autoTrackDeviceAttributes: Boolean,
)
Expand Up @@ -8,7 +8,7 @@ interface ApplicationStore {
val customerAppName: String
val customerAppVersion: String

val isPushSubscribed: Boolean
val isPushEnabled: Boolean
}

internal class ApplicationStoreImp(val context: Context) : ApplicationStore {
Expand All @@ -19,7 +19,7 @@ internal class ApplicationStoreImp(val context: Context) : ApplicationStore {
get() = appInfo.first
override val customerAppVersion: String
get() = appInfo.second
override val isPushSubscribed: Boolean
override val isPushEnabled: Boolean
get() = NotificationManagerCompat.from(context).areNotificationsEnabled()

private fun getAppInformation(): Pair<String, String> {
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/main/java/io/customer/sdk/data/store/BuildStore.kt
Expand Up @@ -17,7 +17,7 @@ interface BuildStore {
// Android SDK Version: 21
val deviceOSVersion: Int

// Device locale: en
// Device locale: en-US
val deviceLocale: String
}

Expand All @@ -32,5 +32,5 @@ internal class BuildStoreImp : BuildStore {
override val deviceOSVersion: Int
get() = Build.VERSION.SDK_INT
override val deviceLocale: String
get() = Locale.getDefault().language
get() = Locale.getDefault().toLanguageTag()
}
6 changes: 3 additions & 3 deletions sdk/src/main/java/io/customer/sdk/data/store/DeviceStore.kt
Expand Up @@ -39,8 +39,8 @@ internal class DeviceStoreImp(
get() = applicationStore.customerAppName
override val customerAppVersion: String
get() = applicationStore.customerAppVersion
override val isPushSubscribed: Boolean
get() = applicationStore.isPushSubscribed
override val isPushEnabled: Boolean
get() = applicationStore.isPushEnabled
override val customerIOVersion: String
get() = version

Expand All @@ -60,7 +60,7 @@ internal class DeviceStoreImp(
"app_version" to customerAppVersion,
"cio_sdk_version" to customerIOVersion,
"device_locale" to deviceLocale,
"push_subscribed" to isPushSubscribed
"push_enabled" to isPushEnabled
)
}
}
103 changes: 95 additions & 8 deletions sdk/src/test/java/io/customer/sdk/CustomerIOTest.kt
@@ -1,18 +1,24 @@
package io.customer.sdk

import android.net.Uri
import io.customer.base.data.ErrorResult
import io.customer.base.error.ErrorDetail
import io.customer.base.error.StatusCode
import io.customer.base.utils.ActionUtils.Companion.getEmptyAction
import io.customer.base.utils.ActionUtils.Companion.getErrorAction
import io.customer.sdk.MockCustomerIOBuilder.Companion.defaultConfig
import io.customer.sdk.data.communication.CustomerIOUrlHandler
import io.customer.sdk.data.model.Region
import io.customer.sdk.data.request.MetricEvent
import io.customer.sdk.utils.verifyError
import io.customer.sdk.utils.verifySuccess
import org.amshove.kluent.`should be equal to`
import org.amshove.kluent.shouldBeEqualTo
import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.`when`
import org.mockito.kotlin.any
import org.mockito.kotlin.verify

internal class CustomerIOTest : BaseTest() {

Expand All @@ -28,13 +34,44 @@ internal class CustomerIOTest : BaseTest() {
}

@Test
fun `verify SDK configuration is correct`() {
customerIO.config.siteId `should be equal to` MockCustomerIOBuilder.siteId
customerIO.config.apiKey `should be equal to` MockCustomerIOBuilder.apiKey
customerIO.config.timeout `should be equal to` MockCustomerIOBuilder.timeout.toLong()
customerIO.config.region `should be equal to` MockCustomerIOBuilder.region
customerIO.config.urlHandler `should be equal to` MockCustomerIOBuilder.urlHandler
customerIO.config.autoTrackScreenViews `should be equal to` MockCustomerIOBuilder.shouldAutoRecordScreenViews
fun `verify SDK default configuration is correct`() {
customerIO.config.siteId shouldBeEqualTo MockCustomerIOBuilder.siteId
customerIO.config.apiKey shouldBeEqualTo MockCustomerIOBuilder.apiKey
customerIO.config.timeout shouldBeEqualTo MockCustomerIOBuilder.timeout.toLong()
customerIO.config.region shouldBeEqualTo MockCustomerIOBuilder.region
customerIO.config.urlHandler shouldBeEqualTo MockCustomerIOBuilder.urlHandler
customerIO.config.autoTrackScreenViews shouldBeEqualTo MockCustomerIOBuilder.shouldAutoRecordScreenViews
customerIO.config.autoTrackDeviceAttributes shouldBeEqualTo MockCustomerIOBuilder.autoTrackDeviceAttributes
}

@Test
fun verify_onUpdatingBuilderConfigurations_expectCustomerIOOConfigToBeUpdated() {

val mockCustomerIOBuilder =
MockCustomerIOBuilder(
defaultConfig.copy(
autoTrackDeviceAttributes = false,
autoTrackScreenViews = true,
siteId = "new-id",
apiKey = "new-key",
region = Region.EU,
urlHandler = object : CustomerIOUrlHandler {
override fun handleCustomerIOUrl(uri: Uri): Boolean {
return true
}
},
timeout = 9000
)
)
customerIO = mockCustomerIOBuilder.build()

customerIO.config.siteId shouldNotBeEqualTo defaultConfig.siteId
customerIO.config.apiKey shouldNotBeEqualTo defaultConfig.apiKey
customerIO.config.timeout shouldNotBeEqualTo defaultConfig.timeout
customerIO.config.region shouldNotBeEqualTo defaultConfig.region
customerIO.config.urlHandler shouldNotBeEqualTo defaultConfig.urlHandler
customerIO.config.autoTrackScreenViews shouldNotBeEqualTo defaultConfig.autoTrackScreenViews
customerIO.config.autoTrackDeviceAttributes shouldNotBeEqualTo defaultConfig.autoTrackDeviceAttributes
}

@Test
Expand Down Expand Up @@ -156,6 +193,56 @@ internal class CustomerIOTest : BaseTest() {
verifySuccess(response, Unit)
}

@Test
fun verify_bothDefaultAndCustomAttributesGetsAdded_withRegisterToken() {

`when`(
mockCustomerIO.api.registerDeviceToken(
any(),
any(),
)
).thenReturn(getEmptyAction())

customerIO.deviceAttributes.putAll(mapOf("test" to "value"))

val expectedToken = "token"

val expectedAttributes = customerIO.deviceAttributes + deviceStore.buildDeviceAttributes()

val response = customerIO.registerDeviceToken(expectedToken).execute()

verify(mockCustomerIO.api).registerDeviceToken(expectedToken, expectedAttributes)

verifySuccess(response, Unit)
}

@Test
fun verify_defaultAttributesGetsSkipped_onBasisOfConfig() {

val mockCustomerIO =
MockCustomerIOBuilder(MockCustomerIOBuilder.defaultConfig.copy(autoTrackDeviceAttributes = false))
customerIO = mockCustomerIO.build()

`when`(mockCustomerIO.store.deviceStore).thenReturn(deviceStore)

`when`(
mockCustomerIO.api.registerDeviceToken(
any(),
any(),
)
).thenReturn(getEmptyAction())

customerIO.deviceAttributes.putAll(mapOf("test" to "value"))

val expectedToken = "token"

val expectedAttributes = mapOf("test" to "value")

customerIO.registerDeviceToken(expectedToken).execute()

verify(mockCustomerIO.api).registerDeviceToken(expectedToken, expectedAttributes)
}

@Test
fun `verify SDK returns error when adding device request fails`() {
`when`(
Expand Down
13 changes: 8 additions & 5 deletions sdk/src/test/java/io/customer/sdk/MockCustomerIOBuilder.kt
Expand Up @@ -5,7 +5,7 @@ import io.customer.sdk.data.model.Region
import io.customer.sdk.data.store.CustomerIOStore
import org.mockito.kotlin.mock

internal class MockCustomerIOBuilder {
internal class MockCustomerIOBuilder(private val customerIOConfig: CustomerIOConfig = defaultConfig) {

lateinit var api: CustomerIOApi
lateinit var store: CustomerIOStore
Expand All @@ -18,17 +18,20 @@ internal class MockCustomerIOBuilder {
const val timeout = 6000
val urlHandler = null
const val shouldAutoRecordScreenViews = false
}
const val autoTrackDeviceAttributes = true

fun build(): CustomerIO {
val customerIOConfig = CustomerIOConfig(
val defaultConfig = CustomerIOConfig(
apiKey = "mock-key",
siteId = "mock-site",
region = Region.US,
timeout = 6000,
urlHandler = urlHandler,
autoTrackScreenViews = shouldAutoRecordScreenViews
autoTrackScreenViews = shouldAutoRecordScreenViews,
autoTrackDeviceAttributes = autoTrackDeviceAttributes
)
}

fun build(): CustomerIO {

api = mock()
store = mock()
Expand Down
6 changes: 3 additions & 3 deletions sdk/src/test/java/io/customer/sdk/store/DeviceStoreTest.kt
Expand Up @@ -13,7 +13,7 @@ internal class DeviceStoreTest : BaseTest() {
deviceStore.deviceModel `should be equal to` "Pixel 6"
deviceStore.deviceManufacturer `should be equal to` "Google"
deviceStore.deviceOSVersion `should be equal to` 30
deviceStore.deviceLocale `should be equal to` "en"
deviceStore.deviceLocale `should be equal to` "en-US"
}

@Test
Expand All @@ -36,8 +36,8 @@ internal class DeviceStoreTest : BaseTest() {
"device_model" to "Pixel 6",
"app_version" to "1.0",
"cio_sdk_version" to "1.0.0-alpha.6",
"device_locale" to "en",
"push_subscribed" to true
"device_locale" to "en-US",
"push_enabled" to true
)

resultDeviceAttributes shouldBeEqualTo expectedDeviceAttributes
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/test/java/io/customer/sdk/utils/DeviceStoreStub.kt
Expand Up @@ -20,14 +20,14 @@ class DeviceStoreStub {
override val deviceOSVersion: Int
get() = 30
override val deviceLocale: String
get() = Locale.US.language
get() = Locale.US.toLanguageTag()
},
applicationStore = object : ApplicationStore {
override val customerAppName: String
get() = "User App"
override val customerAppVersion: String
get() = "1.0"
override val isPushSubscribed: Boolean
override val isPushEnabled: Boolean
get() = true
},
version = "1.0.0-alpha.6"
Expand Down

0 comments on commit a7dbaba

Please sign in to comment.