Skip to content

Commit

Permalink
WMSDK-404: request config after reopen in-app session (#532)
Browse files Browse the repository at this point in the history
  • Loading branch information
sergeysozinov authored Feb 3, 2025
1 parent 5403824 commit 399940e
Show file tree
Hide file tree
Showing 15 changed files with 110 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal class SessionStorageManager(private val timeProvider: TimeProvider) {
var shownInAppIdsWithEvents = mutableMapOf<String, MutableSet<Int>>()
var configFetchingError: Boolean = false
var lastTrackVisitSendTime: Long = 0L
var sessionTime: Long = 20000L
var sessionTime: Long = 0L

private val sessionExpirationListeners = mutableListOf<SessionExpirationListener>()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,8 @@ internal class InAppRepositoryImpl(
override fun isInAppShown(): Boolean {
return sessionStorageManager.isInAppMessageShown
}

override fun clearInAppEvents() {
MindboxEventManager.resetEventFlowCache()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cloud.mindbox.mobile_sdk.inapp.data.repositories

import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.getOrNull
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.data.managers.data_filler.DataManager
import cloud.mindbox.mobile_sdk.inapp.data.mapper.InAppMapper
Expand All @@ -10,19 +11,17 @@ import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.repositories.MobileConfi
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.validators.InAppValidator
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppConfig
import cloud.mindbox.mobile_sdk.inapp.domain.models.InAppTtlData
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.logger.mindboxLogE
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.logger.mindboxLogW
import cloud.mindbox.mobile_sdk.managers.DbManager
import cloud.mindbox.mobile_sdk.managers.GatewayManager
import cloud.mindbox.mobile_sdk.models.operation.response.*
import cloud.mindbox.mobile_sdk.monitoring.data.validators.MonitoringValidator
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.utils.suspendLazy
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

Expand All @@ -43,8 +42,15 @@ internal class MobileConfigRepositoryImpl(

private val mutex = Mutex()

private val inAppConfig = Mindbox.mindboxScope.suspendLazy {
listenInAppConfig().first()
private val configState = MutableStateFlow<InAppConfig?>(null)

init {
Mindbox.mindboxScope.launch {
MindboxPreferences.inAppConfigFlow
.collectLatest { configString ->
processConfigUpdate(configString)
}
}
}

override suspend fun fetchMobileConfig() {
Expand All @@ -55,42 +61,32 @@ internal class MobileConfigRepositoryImpl(
MindboxPreferences.inAppConfigUpdatedTime = System.currentTimeMillis()
}

private fun listenInAppConfig(): Flow<InAppConfig> {
return MindboxPreferences.inAppConfigFlow.map { inAppConfigString ->
mutex.withLock {
this@MobileConfigRepositoryImpl.mindboxLogD(
message = "CachedConfig : $inAppConfigString"
)
val configBlank =
mobileConfigSerializationManager.deserializeToConfigDtoBlank(inAppConfigString)

val filteredConfig = InAppConfigResponse(
inApps = runCatching { getInApps(configBlank) }.getOrElse {
mindboxLogW("Unable to get inApps $it")
null
},
monitoring = runCatching { getMonitoring(configBlank) }.getOrElse {
mindboxLogW("Unable to get logs $it")
null
},
settings = runCatching { getSettings(configBlank) }.getOrElse {
mindboxLogW("Unable to get settings $it")
null
},
abtests = runCatching { getABTests(configBlank) }.getOrElse {
mindboxLogW("Unable to get abtests $it")
null
},
)
private suspend fun processConfigUpdate(inAppConfigString: String) {
mutex.withLock {
this@MobileConfigRepositoryImpl.mindboxLogD(
message = "CachedConfig : $inAppConfigString"
)
val configBlank =
mobileConfigSerializationManager.deserializeToConfigDtoBlank(inAppConfigString)

val filteredConfig = InAppConfigResponse(
inApps = runCatching { getInApps(configBlank) }.getOrNull {
mindboxLogW("Unable to get inApps $it")
},
monitoring = runCatching { getMonitoring(configBlank) }.getOrNull {
mindboxLogW("Unable to get logs $it")
},
settings = runCatching { getSettings(configBlank) }.getOrNull {
mindboxLogW("Unable to get settings $it")
},
abtests = runCatching { getABTests(configBlank) }.getOrNull {
mindboxLogW("Unable to get abtests $it")
},
)

return@map inAppMapper.mapToInAppConfig(filteredConfig)
.also { inAppConfig ->
MindboxLoggerImpl.d(
parent = this@MobileConfigRepositoryImpl,
message = "Providing config: $inAppConfig"
)
}
}
var updatedInAppConfig = inAppMapper.mapToInAppConfig(filteredConfig)
configState.value = updatedInAppConfig
mindboxLogI(message = "Providing config: $updatedInAppConfig")
}
}

Expand All @@ -102,6 +98,10 @@ internal class MobileConfigRepositoryImpl(

override suspend fun getABTests() = getConfig().abtests

override fun resetCurrentConfig() {
configState.value = null
}

private fun getInApps(configBlank: InAppConfigResponseBlank?): List<InAppDto>? {
val isValidConfig = inAppConfigTtlValidator.isValid(
InAppTtlData(
Expand Down Expand Up @@ -183,5 +183,9 @@ internal class MobileConfigRepositoryImpl(
}
}

private suspend fun getConfig(): InAppConfig = inAppConfig.invoke()
private suspend fun getConfig(): InAppConfig {
return configState
.filterNotNull()
.first()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ internal class InAppInteractorImpl(
val inAppsMap = inAppRepository.getTargetedInApps()
logI("Whole InApp list = $inApps")
logI("InApps that has already sent targeting ${inAppsMap.entries}")
inAppTargetingChannel.consumeAsFlow().collect { event ->
inAppTargetingChannel.receiveAsFlow().collect { event ->
val filteredInApps = inAppFilteringManager.filterInAppsByEvent(inApps, event)
logI("inapps for event $event are = $filteredInApps")
for (inApp in filteredInApps) {
Expand All @@ -119,4 +119,9 @@ internal class InAppInteractorImpl(
override suspend fun fetchMobileConfig() {
mobileConfigRepository.fetchMobileConfig()
}

override fun resetInAppConfigAndEvents() {
mobileConfigRepository.resetCurrentConfig()
inAppRepository.clearInAppEvents()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@ internal interface InAppInteractor {
fun sendInAppClicked(inAppId: String)

suspend fun fetchMobileConfig()

fun resetInAppConfigAndEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ internal interface InAppRepository {
fun setInAppShown()

fun isInAppShown(): Boolean

fun clearInAppEvents()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ internal interface MobileConfigRepository {
suspend fun getOperations(): Map<OperationName, OperationSystemName>

suspend fun getABTests(): List<ABTest>

fun resetCurrentConfig()
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ internal interface InAppMessageManager {
fun initLogs()

fun onResumeCurrentActivity(activity: Activity, shouldUseBlur: Boolean)

fun handleSessionExpiration()
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package cloud.mindbox.mobile_sdk.inapp.presentation

import android.app.Activity
import cloud.mindbox.mobile_sdk.InitializeLock
import cloud.mindbox.mobile_sdk.Mindbox
import cloud.mindbox.mobile_sdk.inapp.data.managers.SessionStorageManager
import cloud.mindbox.mobile_sdk.inapp.domain.interfaces.interactors.InAppInteractor
import cloud.mindbox.mobile_sdk.logger.MindboxLoggerImpl
import cloud.mindbox.mobile_sdk.logger.mindboxLogD
import cloud.mindbox.mobile_sdk.logger.mindboxLogI
import cloud.mindbox.mobile_sdk.managers.MindboxEventManager
import cloud.mindbox.mobile_sdk.monitoring.domain.interfaces.MonitoringInteractor
import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
Expand All @@ -23,10 +25,13 @@ internal class InAppMessageManagerImpl(

init {
sessionStorageManager.addSessionExpirationListener {
mindboxLogI("Session expired.Start a new session right now!")
mindboxLogI("Start a new session now!")
handleSessionExpiration()
}
}

private var processingJob: Job? = null

override fun registerCurrentActivity(activity: Activity) {
LoggingExceptionHandler.runCatching {
inAppMessageViewDisplayer.registerCurrentActivity(activity, true)
Expand All @@ -37,7 +42,7 @@ internal class InAppMessageManagerImpl(
CoroutineScope(defaultDispatcher + SupervisorJob() + Mindbox.coroutineExceptionHandler)

override fun listenEventAndInApp() {
inAppScope.launch {
processingJob = inAppScope.launch {
launch {
inAppInteractor.listenToTargetingEvents()
}
Expand All @@ -54,7 +59,6 @@ internal class InAppMessageManagerImpl(
this@InAppMessageManagerImpl.mindboxLogD("Inapp already shown. Skip ${inAppMessage.inAppId}")
return@withContext
}

inAppMessageViewDisplayer.tryShowInAppMessage(
inAppType = inAppMessage,
onInAppClick = {
Expand Down Expand Up @@ -92,6 +96,7 @@ internal class InAppMessageManagerImpl(
}
}
} else {
MindboxPreferences.inAppConfig = MindboxPreferences.inAppConfig
MindboxLoggerImpl.e(
this@InAppMessageManagerImpl::class,
"Failed to get config",
Expand Down Expand Up @@ -131,6 +136,19 @@ internal class InAppMessageManagerImpl(
}
}

override fun handleSessionExpiration() {
inAppScope.launch {
inAppMessageViewDisplayer.hideCurrentInApp()
processingJob?.cancel()
inAppInteractor.resetInAppConfigAndEvents()
InitializeLock.reset(InitializeLock.State.APP_STARTED)
listenEventAndInApp()
initLogs()
MindboxEventManager.eventFlow.emit(MindboxEventManager.appStarted())
requestConfig().join()
}
}

companion object {
const val CONFIG_NOT_FOUND = 404
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ internal interface InAppMessageViewDisplayer {
fun registerInAppCallback(inAppCallback: InAppCallback)

fun isInAppActive(): Boolean

fun hideCurrentInApp()
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,13 @@ internal class InAppMessageViewDisplayerImpl(private val inAppImageSizeStorage:
mindboxLogE("failed to show inApp: currentRoot is null")
}
}

override fun hideCurrentInApp() {
currentHolder?.hide()
pausedHolder?.hide()
currentHolder = null
pausedHolder = null
inAppQueue.clear()
mindboxLogI("Hide active in-app if it's present")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ internal abstract class AbstractInAppViewHolder<T : InAppType> : InAppViewHolder

override fun hide() {
(currentDialog.parent as? ViewGroup?)?.apply {
removeView(currentDialog)
post { removeView(currentDialog) }
}
mindboxLogI("hide ${wrapper.inAppType.inAppId} on ${this.hashCode()}")
restoreKeyboard()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ internal class ModalWindowInAppViewHolder(

override fun hide() {
(currentDialog.parent as? ViewGroup?)?.apply {
removeView(currentBackground)
post { removeView(currentBackground) }
}
super.hide()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import cloud.mindbox.mobile_sdk.repository.MindboxPreferences
import cloud.mindbox.mobile_sdk.services.BackgroundWorkManager
import cloud.mindbox.mobile_sdk.utils.LoggingExceptionHandler
import com.google.gson.Gson
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -211,4 +212,9 @@ internal object MindboxEventManager {
}

fun <T> operationBodyJson(body: T): String = gson.toJson(body)

@OptIn(ExperimentalCoroutinesApi::class)
fun resetEventFlowCache() {
eventFlow.resetReplayCache()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ internal class InAppMessageManagerTest {
sessionStorageManager
)
mockkObject(LoggingExceptionHandler)
every { MindboxPreferences.inAppConfig } returns "test"
coEvery {
MindboxLoggerImpl.e(any(), any())
} just runs
Expand All @@ -109,6 +110,9 @@ internal class InAppMessageManagerTest {
verify(exactly = 1) {
MindboxLoggerImpl.e(InAppMessageManagerImpl::class, "Failed to get config", error)
}
verify(exactly = 1) {
MindboxPreferences setProperty MindboxPreferences::inAppConfig.name value "test"
}
}

@Test
Expand Down

0 comments on commit 399940e

Please sign in to comment.