Skip to content

Commit 91b7617

Browse files
authored
Ensure that we have only one single instance of SeenInviteStore per session (#4577)
* Ensure that we have only one single instance of SeenInviteStore per session. Fixes #4558 Fix crash: java.lang.IllegalStateException: There are multiple DataStores active for the same file: /data/user/0/io.element.android.x/files/datastore/session_0ebb139587b6d940_seen-invites.preferences_pb. You should either maintain your DataStore as a singleton or confirm that there is no two DataStore's active on the same file (by confirming that the scope is cancelled). * Inject the SeenInvitesStore to reduce boilerplate code.
1 parent 62a2c2f commit 91b7617

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/DefaultSeenInvitesStore.kt

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,25 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
1212
import androidx.datastore.preferences.core.edit
1313
import androidx.datastore.preferences.core.stringSetPreferencesKey
1414
import androidx.datastore.preferences.preferencesDataStoreFile
15-
import com.squareup.anvil.annotations.ContributesBinding
1615
import io.element.android.features.invite.api.SeenInvitesStore
1716
import io.element.android.libraries.androidutils.file.safeDelete
1817
import io.element.android.libraries.androidutils.hash.hash
19-
import io.element.android.libraries.di.ApplicationContext
20-
import io.element.android.libraries.di.SessionScope
21-
import io.element.android.libraries.di.SingleIn
22-
import io.element.android.libraries.di.annotations.SessionCoroutineScope
2318
import io.element.android.libraries.matrix.api.core.RoomId
2419
import io.element.android.libraries.matrix.api.core.SessionId
25-
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
2620
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
2721
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
2822
import kotlinx.coroutines.CoroutineScope
2923
import kotlinx.coroutines.flow.Flow
3024
import kotlinx.coroutines.flow.map
31-
import javax.inject.Inject
3225

3326
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
3427

35-
@SingleIn(SessionScope::class)
36-
@ContributesBinding(SessionScope::class)
37-
class DefaultSeenInvitesStore @Inject constructor(
38-
@ApplicationContext context: Context,
39-
currentSessionIdHolder: CurrentSessionIdHolder,
40-
@SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
28+
class DefaultSeenInvitesStore(
29+
context: Context,
30+
sessionId: SessionId,
31+
sessionCoroutineScope: CoroutineScope,
4132
sessionObserver: SessionObserver,
4233
) : SeenInvitesStore {
43-
private val sessionId: SessionId = currentSessionIdHolder.current
44-
4534
init {
4635
sessionObserver.addListener(object : SessionListener {
4736
override suspend fun onSessionCreated(userId: String) = Unit
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.invite.impl
9+
10+
import android.content.Context
11+
import com.squareup.anvil.annotations.ContributesBinding
12+
import io.element.android.features.invite.api.SeenInvitesStore
13+
import io.element.android.libraries.di.AppScope
14+
import io.element.android.libraries.di.ApplicationContext
15+
import io.element.android.libraries.di.SingleIn
16+
import io.element.android.libraries.matrix.api.core.SessionId
17+
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
18+
import kotlinx.coroutines.CoroutineScope
19+
import java.util.concurrent.ConcurrentHashMap
20+
import javax.inject.Inject
21+
22+
@SingleIn(AppScope::class)
23+
@ContributesBinding(AppScope::class)
24+
class DefaultSeenInvitesStoreFactory @Inject constructor(
25+
@ApplicationContext private val context: Context,
26+
private val sessionObserver: SessionObserver,
27+
) : SeenInvitesStoreFactory {
28+
// We can have only one class accessing a single data store, so keep a cache of them.
29+
private val cache = ConcurrentHashMap<SessionId, SeenInvitesStore>()
30+
31+
override fun getOrCreate(
32+
sessionId: SessionId,
33+
sessionCoroutineScope: CoroutineScope,
34+
): SeenInvitesStore {
35+
return cache.getOrPut(sessionId) {
36+
DefaultSeenInvitesStore(
37+
context = context,
38+
sessionId = sessionId,
39+
sessionCoroutineScope = sessionCoroutineScope,
40+
sessionObserver = sessionObserver,
41+
)
42+
}
43+
}
44+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.invite.impl
9+
10+
import io.element.android.features.invite.api.SeenInvitesStore
11+
import io.element.android.libraries.matrix.api.core.SessionId
12+
import kotlinx.coroutines.CoroutineScope
13+
14+
interface SeenInvitesStoreFactory {
15+
fun getOrCreate(
16+
sessionId: SessionId,
17+
sessionCoroutineScope: CoroutineScope,
18+
): SeenInvitesStore
19+
}

features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,31 @@ package io.element.android.features.invite.impl.di
1010
import com.squareup.anvil.annotations.ContributesTo
1111
import dagger.Binds
1212
import dagger.Module
13+
import dagger.Provides
14+
import io.element.android.features.invite.api.SeenInvitesStore
1315
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
16+
import io.element.android.features.invite.impl.SeenInvitesStoreFactory
1417
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
1518
import io.element.android.libraries.architecture.Presenter
1619
import io.element.android.libraries.di.SessionScope
20+
import io.element.android.libraries.matrix.api.MatrixClient
1721

1822
@ContributesTo(SessionScope::class)
1923
@Module
2024
interface InviteModule {
2125
@Binds
2226
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
27+
28+
companion object {
29+
@Provides
30+
fun providesSeenInvitesStore(
31+
factory: SeenInvitesStoreFactory,
32+
matrixClient: MatrixClient,
33+
): SeenInvitesStore {
34+
return factory.getOrCreate(
35+
matrixClient.sessionId,
36+
matrixClient.sessionCoroutineScope,
37+
)
38+
}
39+
}
2340
}

0 commit comments

Comments
 (0)