Remember flows (#4533)

* Add Konsist test to ensure that the result of a function returning a flow is remembered.

* Remember flows before they are collected by state.

* Fix compilation issue

* Make isOnline a val.

* Make selectedUsers() a val.

* Make flow() a val.

* Make getUserConsent(), didAskUserConsent() and getAnalyticsId() some val.

* Remove Timeline.paginationStatus() and replace by direct access to the underlined flow.

* Simplify test

* userConsentFlow must be initialized before because it's used in observeUserConsent

* Fix test compilation
This commit is contained in:
Benoit Marty
2025-04-04 16:50:43 +02:00
committed by GitHub
parent 3e90b3c26d
commit 77a7c0b2e5
52 changed files with 221 additions and 172 deletions

View File

@@ -19,9 +19,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker {
fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider>
/**
* Return a Flow of Boolean, true if the user has given their consent.
* A Flow of Boolean, true if the user has given their consent.
*/
fun getUserConsent(): Flow<Boolean>
val userConsentFlow: Flow<Boolean>
/**
* Update the user consent value.
@@ -29,9 +29,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker {
suspend fun setUserConsent(userConsent: Boolean)
/**
* Return a Flow of Boolean, true if the user has been asked for their consent.
* A Flow of Boolean, true if the user has been asked for their consent.
*/
fun didAskUserConsent(): Flow<Boolean>
val didAskUserConsentFlow: Flow<Boolean>
/**
* Store the fact that the user has been asked for their consent.
@@ -39,9 +39,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker {
suspend fun setDidAskUserConsent()
/**
* Return a Flow of String, used for analytics Id.
* A Flow of String, used for analytics Id.
*/
fun getAnalyticsId(): Flow<String>
val analyticsIdFlow: Flow<String>
/**
* Update analyticsId from the AccountData.

View File

@@ -43,6 +43,10 @@ class DefaultAnalyticsService @Inject constructor(
// Cache for the properties to send
private var pendingUserProperties: UserProperties? = null
override val userConsentFlow: Flow<Boolean> = analyticsStore.userConsentFlow
override val didAskUserConsentFlow: Flow<Boolean> = analyticsStore.didAskUserConsentFlow
override val analyticsIdFlow: Flow<String> = analyticsStore.analyticsIdFlow
init {
observeUserConsent()
observeSessions()
@@ -52,19 +56,11 @@ class DefaultAnalyticsService @Inject constructor(
return analyticsProviders
}
override fun getUserConsent(): Flow<Boolean> {
return analyticsStore.userConsentFlow
}
override suspend fun setUserConsent(userConsent: Boolean) {
Timber.tag(analyticsTag.value).d("setUserConsent($userConsent)")
analyticsStore.setUserConsent(userConsent)
}
override fun didAskUserConsent(): Flow<Boolean> {
return analyticsStore.didAskUserConsentFlow
}
override suspend fun setDidAskUserConsent() {
Timber.tag(analyticsTag.value).d("setDidAskUserConsent()")
analyticsStore.setDidAskUserConsent()
@@ -74,10 +70,6 @@ class DefaultAnalyticsService @Inject constructor(
analyticsStore.setDidAskUserConsent(false)
}
override fun getAnalyticsId(): Flow<String> {
return analyticsStore.analyticsIdFlow
}
override suspend fun setAnalyticsId(analyticsId: String) {
Timber.tag(analyticsTag.value).d("setAnalyticsId($analyticsId)")
analyticsStore.setAnalyticsId(analyticsId)
@@ -93,7 +85,7 @@ class DefaultAnalyticsService @Inject constructor(
}
private fun observeUserConsent() {
getUserConsent()
userConsentFlow
.onEach { consent ->
Timber.tag(analyticsTag.value).d("User consent updated to $consent")
userConsent.set(consent)

View File

@@ -132,10 +132,10 @@ class DefaultAnalyticsServiceTest {
analyticsStore = store,
)
assertThat(store.userConsentFlow.first()).isFalse()
assertThat(sut.getUserConsent().first()).isFalse()
assertThat(sut.userConsentFlow.first()).isFalse()
sut.setUserConsent(true)
assertThat(store.userConsentFlow.first()).isTrue()
assertThat(sut.getUserConsent().first()).isTrue()
assertThat(sut.userConsentFlow.first()).isTrue()
}
@Test
@@ -146,10 +146,10 @@ class DefaultAnalyticsServiceTest {
analyticsStore = store,
)
assertThat(store.analyticsIdFlow.first()).isEqualTo("")
assertThat(sut.getAnalyticsId().first()).isEqualTo("")
assertThat(sut.analyticsIdFlow.first()).isEqualTo("")
sut.setAnalyticsId(AN_ID)
assertThat(store.analyticsIdFlow.first()).isEqualTo(AN_ID)
assertThat(sut.getAnalyticsId().first()).isEqualTo(AN_ID)
assertThat(sut.analyticsIdFlow.first()).isEqualTo(AN_ID)
}
@Test
@@ -160,10 +160,10 @@ class DefaultAnalyticsServiceTest {
analyticsStore = store,
)
assertThat(store.didAskUserConsentFlow.first()).isFalse()
assertThat(sut.didAskUserConsent().first()).isFalse()
assertThat(sut.didAskUserConsentFlow.first()).isFalse()
sut.setDidAskUserConsent()
assertThat(store.didAskUserConsentFlow.first()).isTrue()
assertThat(sut.didAskUserConsent().first()).isTrue()
assertThat(sut.didAskUserConsentFlow.first()).isTrue()
}
@Test

View File

@@ -24,11 +24,11 @@ import javax.inject.Inject
@ContributesBinding(AppScope::class)
class NoopAnalyticsService @Inject constructor() : AnalyticsService {
override fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider> = emptySet()
override fun getUserConsent(): Flow<Boolean> = flowOf(false)
override val userConsentFlow: Flow<Boolean> = flowOf(false)
override suspend fun setUserConsent(userConsent: Boolean) = Unit
override fun didAskUserConsent(): Flow<Boolean> = flowOf(true)
override val didAskUserConsentFlow: Flow<Boolean> = flowOf(true)
override suspend fun setDidAskUserConsent() = Unit
override fun getAnalyticsId(): Flow<String> = flowOf("")
override val analyticsIdFlow: Flow<String> = flowOf("")
override suspend fun setAnalyticsId(analyticsId: String) = Unit
override suspend fun reset() = Unit
override fun capture(event: VectorAnalyticsEvent) = Unit

View File

@@ -15,6 +15,7 @@ import io.element.android.services.analytics.api.AnalyticsService
import io.element.android.services.analyticsproviders.api.AnalyticsProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAnalyticsService(
isEnabled: Boolean = false,
@@ -22,7 +23,7 @@ class FakeAnalyticsService(
private val resetLambda: () -> Unit = {},
) : AnalyticsService {
private val isEnabledFlow = MutableStateFlow(isEnabled)
private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
override val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent)
val capturedEvents = mutableListOf<VectorAnalyticsEvent>()
val screenEvents = mutableListOf<VectorAnalyticsScreen>()
val trackedErrors = mutableListOf<Throwable>()
@@ -30,19 +31,17 @@ class FakeAnalyticsService(
override fun getAvailableAnalyticsProviders(): Set<AnalyticsProvider> = emptySet()
override fun getUserConsent(): Flow<Boolean> = isEnabledFlow
override val userConsentFlow: Flow<Boolean> = isEnabledFlow.asStateFlow()
override suspend fun setUserConsent(userConsent: Boolean) {
isEnabledFlow.value = userConsent
}
override fun didAskUserConsent(): Flow<Boolean> = didAskUserConsentFlow
override suspend fun setDidAskUserConsent() {
didAskUserConsentFlow.value = true
}
override fun getAnalyticsId(): Flow<String> = MutableStateFlow("")
override val analyticsIdFlow: Flow<String> = MutableStateFlow("")
override suspend fun setAnalyticsId(analyticsId: String) {
}