Use SessionObserver instead of SessionStore to observe sessions

This commit is contained in:
Jorge Martín 2025-12-05 17:07:54 +01:00 committed by Jorge Martin Espinosa
parent 221e18d139
commit 7eae3a16ae
2 changed files with 16 additions and 41 deletions

View file

@ -11,41 +11,31 @@ package io.element.android.libraries.workmanager.impl
import androidx.work.WorkManager import androidx.work.WorkManager
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.core.coroutine.withPreviousValue
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.observer.SessionListener
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import io.element.android.libraries.workmanager.api.WorkManagerRequest import io.element.android.libraries.workmanager.api.WorkManagerRequest
import io.element.android.libraries.workmanager.api.WorkManagerRequestType import io.element.android.libraries.workmanager.api.WorkManagerRequestType
import io.element.android.libraries.workmanager.api.WorkManagerScheduler import io.element.android.libraries.workmanager.api.WorkManagerScheduler
import io.element.android.libraries.workmanager.api.workManagerTag import io.element.android.libraries.workmanager.api.workManagerTag
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import timber.log.Timber import timber.log.Timber
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultWorkManagerScheduler( class DefaultWorkManagerScheduler(
lazyWorkManager: Lazy<WorkManager>, lazyWorkManager: Lazy<WorkManager>,
@AppCoroutineScope private val appCoroutineScope: CoroutineScope, sessionObserver: SessionObserver,
sessionStore: SessionStore,
) : WorkManagerScheduler { ) : WorkManagerScheduler {
private val workManager by lazyWorkManager private val workManager by lazyWorkManager
init { init {
// Observe session removals to cancel associated work automatically // Observe session removals to cancel associated work automatically
sessionStore.sessionsFlow() sessionObserver.addListener(object : SessionListener {
.map { sessions -> sessions.map { SessionId(it.userId) } } override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {
.withPreviousValue() val sessionId = SessionId(userId)
.map { (prev, new) -> prev.orEmpty() - new.toSet() } Timber.d("Session deleted for userId: $userId, cancelling associated workmanager requests")
.onEach { removedSessions -> cancel(sessionId)
for (sessionId in removedSessions) {
Timber.d("Session removed for userId: $sessionId, cancelling associated workmanager requests")
cancel(sessionId)
}
} }
.launchIn(appCoroutineScope) })
} }
override fun submit(workManagerRequest: WorkManagerRequest) { override fun submit(workManagerRequest: WorkManagerRequest) {

View file

@ -9,10 +9,8 @@ package io.element.android.libraries.workmanager.impl
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkRequest import androidx.work.WorkRequest
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver
import io.element.android.libraries.sessionstorage.test.aSessionData
import io.element.android.libraries.workmanager.api.WorkManagerRequest import io.element.android.libraries.workmanager.api.WorkManagerRequest
import io.element.android.libraries.workmanager.api.WorkManagerRequestType import io.element.android.libraries.workmanager.api.WorkManagerRequestType
import io.element.android.libraries.workmanager.api.workManagerTag import io.element.android.libraries.workmanager.api.workManagerTag
@ -30,40 +28,31 @@ class DefaultWorkManagerSchedulerTest {
@Test @Test
fun `starts observing sessions on init to remove work for logged out sessions`() = runTest { fun `starts observing sessions on init to remove work for logged out sessions`() = runTest {
val sessionId = "@session1:matrix.org" val sessionId = "@session1:matrix.org"
val sessionStore = InMemorySessionStore(initialList = listOf(aSessionData(sessionId = sessionId))) val sessionObserver = FakeSessionObserver()
val workManager = spyk<WorkManager>() val workManager = spyk<WorkManager>()
DefaultWorkManagerScheduler( DefaultWorkManagerScheduler(
lazyWorkManager = lazy { workManager }, lazyWorkManager = lazy { workManager },
appCoroutineScope = backgroundScope, sessionObserver = sessionObserver,
sessionStore = sessionStore,
) )
// We have a single initial session
assertThat(sessionStore.numberOfSessions()).isEqualTo(1)
runCurrent()
// We remove the session // We remove the session
sessionStore.removeSession(sessionId) sessionObserver.onSessionDeleted(sessionId)
runCurrent() runCurrent()
// The session is now gone and work associated with the session is cancelled // The session is now gone and work associated with the session is cancelled
assertThat(sessionStore.numberOfSessions()).isEqualTo(0)
verify { workManager.cancelAllWorkByTag("notifications-$sessionId") } verify { workManager.cancelAllWorkByTag("notifications-$sessionId") }
} }
@Test @Test
fun `submit builds the request and enqueues it`() = runTest { fun `submit builds the request and enqueues it`() = runTest {
val sessionStore = InMemorySessionStore()
val workManager = spyk<WorkManager>() val workManager = spyk<WorkManager>()
val scheduler = DefaultWorkManagerScheduler( val scheduler = DefaultWorkManagerScheduler(
lazyWorkManager = lazy { workManager }, lazyWorkManager = lazy { workManager },
appCoroutineScope = backgroundScope, sessionObserver = FakeSessionObserver(),
sessionStore = sessionStore,
) )
scheduler.submit(FakeWorkManagerRequest()) scheduler.submit(FakeWorkManagerRequest())
@ -73,13 +62,11 @@ class DefaultWorkManagerSchedulerTest {
@Test @Test
fun `submit won't do anything if building the work request fails`() = runTest { fun `submit won't do anything if building the work request fails`() = runTest {
val sessionStore = InMemorySessionStore()
val workManager = spyk<WorkManager>() val workManager = spyk<WorkManager>()
val scheduler = DefaultWorkManagerScheduler( val scheduler = DefaultWorkManagerScheduler(
lazyWorkManager = lazy { workManager }, lazyWorkManager = lazy { workManager },
appCoroutineScope = backgroundScope, sessionObserver = FakeSessionObserver(),
sessionStore = sessionStore,
) )
scheduler.submit(FakeWorkManagerRequest(result = Result.failure(IllegalStateException("Test error")))) scheduler.submit(FakeWorkManagerRequest(result = Result.failure(IllegalStateException("Test error"))))
@ -89,13 +76,11 @@ class DefaultWorkManagerSchedulerTest {
@Test @Test
fun `cancel will cancel all pending work for a session id`() = runTest { fun `cancel will cancel all pending work for a session id`() = runTest {
val sessionStore = InMemorySessionStore()
val workManager = spyk<WorkManager>() val workManager = spyk<WorkManager>()
val scheduler = DefaultWorkManagerScheduler( val scheduler = DefaultWorkManagerScheduler(
lazyWorkManager = lazy { workManager }, lazyWorkManager = lazy { workManager },
appCoroutineScope = backgroundScope, sessionObserver = FakeSessionObserver(),
sessionStore = sessionStore,
) )
val sessionId = SessionId("@alice:matrix.org") val sessionId = SessionId("@alice:matrix.org")