Dismiss the swipe avatar announcement in the app coroutine scope

HomeView sends HomeEvent.DismissSwipeAvatarAnnouncement right before switching account. Switching
account replaces the LoggedInFlow root node, which destroys HomePresenter and cancels its
composition coroutine scope, so the dismissal could be cancelled before the announcement status
was stored. Use the app coroutine scope instead, which survives the account switch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-10-14 17:56:54 +02:00
parent c3f178985d
commit 9efe0db570
2 changed files with 14 additions and 4 deletions
@@ -28,6 +28,7 @@ import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.indicator.api.IndicatorService
@@ -35,6 +36,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.sessionstorage.api.SessionStore
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.combine
@@ -56,13 +58,15 @@ class HomePresenter(
private val sessionStore: SessionStore,
private val announcementService: AnnouncementService,
private val featureFlagService: FeatureFlagService,
@AppCoroutineScope
private val appCoroutineScope: CoroutineScope,
) : Presenter<HomeState> {
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
@Composable
override fun present(): HomeState {
val coroutineState = rememberCoroutineScope()
val coroutineScope = rememberCoroutineScope()
val matrixUser by client.userProfile.collectAsState()
val totalUnreadCount by produceState(client.totalUnreadCount().getOrDefault(0).toInt()) {
@@ -126,10 +130,11 @@ class HomePresenter(
is HomeEvent.SelectHomeNavigationBarItem -> {
currentHomeNavigationBarItemOrdinal = event.item.ordinal
}
HomeEvent.DismissSwipeAvatarAnnouncement -> coroutineState.launch {
// The user is switching account, which destroys this presenter, so use the app scope to not be cancelled.
HomeEvent.DismissSwipeAvatarAnnouncement -> appCoroutineScope.launch {
announcementService.onAnnouncementDismissed(Announcement.SwipeAvatarToSwitchAccount)
}
HomeEvent.DismissMultiAccountAnnouncement -> coroutineState.launch {
HomeEvent.DismissMultiAccountAnnouncement -> coroutineScope.launch {
announcementService.onAnnouncementDismissed(Announcement.MultiAccount)
}
}
@@ -39,8 +39,10 @@ import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.test
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -292,6 +294,7 @@ class HomePresenterTest {
announcementService = FakeAnnouncementService(
onAnnouncementDismissedResult = onAnnouncementDismissedResult,
),
appCoroutineScope = this,
)
presenter.test {
val state = expectMostRecentItem()
@@ -313,7 +316,7 @@ class HomePresenterTest {
}
}
internal fun createHomePresenter(
internal fun TestScope.createHomePresenter(
client: MatrixClient = FakeMatrixClient(),
syncService: SyncService = FakeSyncService(),
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
@@ -323,6 +326,7 @@ internal fun createHomePresenter(
sessionStore: SessionStore = InMemorySessionStore(),
announcementService: AnnouncementService = FakeAnnouncementService(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
appCoroutineScope: CoroutineScope = backgroundScope,
) = HomePresenter(
client = client,
syncService = syncService,
@@ -334,4 +338,5 @@ internal fun createHomePresenter(
sessionStore = sessionStore,
announcementService = announcementService,
featureFlagService = featureFlagService,
appCoroutineScope = appCoroutineScope,
)