Skip to content

Commit

Permalink
Defer loading of user info until when it's really necessary
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Mar 11, 2024
1 parent 9fcd0e9 commit 41b9ac8
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package io.embrace.android.embracesdk.capture.user

import io.embrace.android.embracesdk.internal.utils.Provider
import io.embrace.android.embracesdk.logging.InternalEmbraceLogger
import io.embrace.android.embracesdk.payload.UserInfo
import io.embrace.android.embracesdk.payload.UserInfo.Companion.ofStored
import io.embrace.android.embracesdk.prefs.PreferencesService
import java.util.concurrent.atomic.AtomicReference
import java.util.regex.Pattern

internal class EmbraceUserService(
private val preferencesService: PreferencesService,
private val logger: InternalEmbraceLogger
) : UserService {

@Volatile
internal var info: UserInfo = ofStored(preferencesService)
/**
* Do not access this directly - use [userInfo] and [modifyUserInfo] to get and set this
*/
private val userInfoReference = AtomicReference(DEFAULT_USER)
private val userInfoProvider: Provider<UserInfo> = { ofStored(preferencesService) }

override fun loadUserInfoFromDisk(): UserInfo? {
return try {
Expand All @@ -23,14 +27,14 @@ internal class EmbraceUserService(
}
}

override fun getUserInfo(): UserInfo = info.copy()
override fun getUserInfo(): UserInfo = userInfo().copy()

override fun setUserIdentifier(userId: String?) {
val currentUserId = info.userId
val currentUserId = userInfo().userId
if (currentUserId != null && currentUserId == userId) {
return
}
info = info.copy(userId = userId)
modifyUserInfo(userInfo().copy(userId = userId))
preferencesService.userIdentifier = userId
}

Expand All @@ -39,11 +43,11 @@ internal class EmbraceUserService(
}

override fun setUsername(username: String?) {
val currentUserName = info.username
val currentUserName = userInfo().username
if (currentUserName != null && currentUserName == username) {
return
}
info = info.copy(username = username)
modifyUserInfo(userInfo().copy(username = username))
preferencesService.username = username
}

Expand All @@ -52,11 +56,11 @@ internal class EmbraceUserService(
}

override fun setUserEmail(email: String?) {
val currentEmail = info.email
val currentEmail = userInfo().email
if (currentEmail != null && currentEmail == email) {
return
}
info = info.copy(email = email)
modifyUserInfo(userInfo().copy(email = email))
preferencesService.userEmailAddress = email
}

Expand All @@ -80,7 +84,7 @@ internal class EmbraceUserService(
logger.logWarning("Ignoring persona " + persona + " as it does not match " + VALID_PERSONA.pattern())
return
}
val currentPersonas = info.personas
val currentPersonas = userInfo().personas
if (currentPersonas != null) {
if (currentPersonas.size >= PERSONA_LIMIT) {
logger.logWarning("Cannot set persona as the limit of " + PERSONA_LIMIT + " has been reached")
Expand All @@ -91,28 +95,28 @@ internal class EmbraceUserService(
}
}

val newPersonas: Set<String> = info.personas?.plus(persona) ?: mutableSetOf(persona)
info = info.copy(personas = newPersonas)
val newPersonas: Set<String> = userInfo().personas?.plus(persona) ?: mutableSetOf(persona)
modifyUserInfo(userInfo().copy(personas = newPersonas))
preferencesService.userPersonas = newPersonas
}

override fun clearUserPersona(persona: String?) {
if (persona == null) {
return
}
val currentPersonas = info.personas
val currentPersonas = userInfo().personas
if (currentPersonas != null && !currentPersonas.contains(persona)) {
logger.logWarning("Persona '$persona' is not set")
return
}

val newPersonas: Set<String> = info.personas?.minus(persona) ?: mutableSetOf()
info = info.copy(personas = newPersonas)
val newPersonas: Set<String> = userInfo().personas?.minus(persona) ?: mutableSetOf()
modifyUserInfo(userInfo().copy(personas = newPersonas))
preferencesService.userPersonas = newPersonas
}

override fun clearAllUserPersonas() {
val currentPersonas = info.personas
val currentPersonas = userInfo().personas
if (currentPersonas != null && currentPersonas.isEmpty()) {
return
}
Expand All @@ -123,7 +127,7 @@ internal class EmbraceUserService(
if (preferencesService.isUsersFirstDay()) {
personas.add(UserInfo.PERSONA_FIRST_DAY_USER)
}
info = info.copy(personas = personas)
modifyUserInfo(userInfo().copy(personas = personas))
preferencesService.userPersonas = personas
}

Expand All @@ -134,11 +138,36 @@ internal class EmbraceUserService(
clearAllUserPersonas()
}

private fun userInfo(): UserInfo {
if (userInfoReference.get() === DEFAULT_USER) {
synchronized(userInfoReference) {
if (userInfoReference.get() === DEFAULT_USER) {
userInfoReference.set(userInfoProvider())
}
}
}

return userInfoReference.get()
}

private fun modifyUserInfo(newUserInfo: UserInfo) {
synchronized(userInfoReference) {
userInfoReference.set(newUserInfo)
}
}

companion object {
// Valid persona regex representation.
val VALID_PERSONA: Pattern = Pattern.compile("^[a-zA-Z0-9_]{1,32}$")

// Maximum number of allowed personas.
const val PERSONA_LIMIT = 10

private val DEFAULT_USER = UserInfo(
userId = "",
email = "",
username = "",
personas = emptySet()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,33 @@ internal class EmbraceUserServiceTest {
@Test
fun testUserInfoNotLoaded() {
mockNoUserInfo()
assertNotNull(service.info)
service.info.verifyNoUserInfo()
assertNotNull(service.getUserInfo())
service.getUserInfo().verifyNoUserInfo()
}

@Test
fun testUserInfoLoaded() {
mockUserInfo()
assertNotNull(service.info)
service.info.verifyExpectedUserInfo()
assertNotNull(service.getUserInfo())
service.getUserInfo().verifyExpectedUserInfo()
}

@Test
fun testUserInfoSessionCopy() {
mockUserInfo()
assertNotSame(service.info, service.getUserInfo())
assertNotSame(service.getUserInfo(), service.getUserInfo())
}

@Test
fun testUserIdentifier() {
mockUserInfo()

with(service) {
assertEquals("f0a923498c", info.userId)
assertEquals("f0a923498c", getUserInfo().userId)
setUserIdentifier("abc")
assertEquals("abc", info.userId)
assertEquals("abc", getUserInfo().userId)
service.clearUserIdentifier()
assertNull(info.userId)
assertNull(getUserInfo().userId)
}
}

Expand All @@ -84,11 +84,11 @@ internal class EmbraceUserServiceTest {
mockUserInfo()

with(service) {
assertEquals("Mr Test", info.username)
assertEquals("Mr Test", getUserInfo().username)
setUsername("Joe")
assertEquals("Joe", info.username)
assertEquals("Joe", getUserInfo().username)
service.clearUsername()
assertNull(info.username)
assertNull(getUserInfo().username)
}
}

Expand All @@ -97,11 +97,11 @@ internal class EmbraceUserServiceTest {
mockUserInfo()

with(service) {
assertEquals("[email protected]", info.email)
assertEquals("[email protected]", getUserInfo().email)
setUserEmail("[email protected]")
assertEquals("[email protected]", info.email)
assertEquals("[email protected]", getUserInfo().email)
service.clearUserEmail()
assertNull(info.email)
assertNull(getUserInfo().email)
}
}

Expand All @@ -110,11 +110,11 @@ internal class EmbraceUserServiceTest {
mockUserInfo()

with(service) {
assertTrue(checkNotNull(info.personas).contains("payer"))
assertTrue(checkNotNull(getUserInfo().personas).contains("payer"))
clearUserAsPayer()
assertFalse(checkNotNull(info.personas).contains("payer"))
assertFalse(checkNotNull(getUserInfo().personas).contains("payer"))
setUserAsPayer()
assertTrue(checkNotNull(info.personas).contains("payer"))
assertTrue(checkNotNull(getUserInfo().personas).contains("payer"))
}
}

Expand All @@ -123,12 +123,12 @@ internal class EmbraceUserServiceTest {
mockUserInfo()

with(service) {
info.verifyExpectedUserInfo()
getUserInfo().verifyExpectedUserInfo()
service.clearAllUserInfo()
assertNull(info.email)
assertNull(info.userId)
assertNull(info.username)
assertEquals(extraPersonas, info.personas)
assertNull(getUserInfo().email)
assertNull(getUserInfo().userId)
assertNull(getUserInfo().username)
assertEquals(extraPersonas, getUserInfo().personas)
}
}

Expand All @@ -137,7 +137,7 @@ internal class EmbraceUserServiceTest {
mockUserInfo()
val persona = "!@£$$%*("
service.addUserPersona(persona)
val personas = checkNotNull(service.info.personas)
val personas = checkNotNull(service.getUserInfo().personas)
assertFalse(personas.contains(persona))
}

Expand All @@ -148,7 +148,7 @@ internal class EmbraceUserServiceTest {
repeat(11) { k ->
service.addUserPersona("Persona_$k")
}
val personas = checkNotNull(service.info.personas)
val personas = checkNotNull(service.getUserInfo().personas)
assertTrue(personas.contains("Persona_1"))
assertTrue(personas.contains("Persona_9"))
assertFalse(personas.contains("Persona_10"))
Expand Down

0 comments on commit 41b9ac8

Please sign in to comment.