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.
This commit is contained in:
parent
ce0bac55c5
commit
dbb96c5212
4 changed files with 84 additions and 15 deletions
|
|
@ -12,36 +12,25 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||||
import androidx.datastore.preferences.core.edit
|
import androidx.datastore.preferences.core.edit
|
||||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||||
import com.squareup.anvil.annotations.ContributesBinding
|
|
||||||
import io.element.android.features.invite.api.SeenInvitesStore
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.libraries.androidutils.file.safeDelete
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.androidutils.hash.hash
|
import io.element.android.libraries.androidutils.hash.hash
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import io.element.android.libraries.di.SingleIn
|
|
||||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder
|
|
||||||
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
|
||||||
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
|
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
|
||||||
|
|
||||||
@SingleIn(SessionScope::class)
|
class DefaultSeenInvitesStore(
|
||||||
@ContributesBinding(SessionScope::class)
|
context: Context,
|
||||||
class DefaultSeenInvitesStore @Inject constructor(
|
sessionId: SessionId,
|
||||||
@ApplicationContext context: Context,
|
sessionCoroutineScope: CoroutineScope,
|
||||||
currentSessionIdHolder: CurrentSessionIdHolder,
|
|
||||||
@SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
|
|
||||||
sessionObserver: SessionObserver,
|
sessionObserver: SessionObserver,
|
||||||
) : SeenInvitesStore {
|
) : SeenInvitesStore {
|
||||||
private val sessionId: SessionId = currentSessionIdHolder.current
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sessionObserver.addListener(object : SessionListener {
|
sessionObserver.addListener(object : SessionListener {
|
||||||
override suspend fun onSessionCreated(userId: String) = Unit
|
override suspend fun onSessionCreated(userId: String) = Unit
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invite.impl
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.squareup.anvil.annotations.ContributesBinding
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
|
import io.element.android.libraries.di.AppScope
|
||||||
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
|
import io.element.android.libraries.di.SingleIn
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@SingleIn(AppScope::class)
|
||||||
|
@ContributesBinding(AppScope::class)
|
||||||
|
class DefaultSeenInvitesStoreFactory @Inject constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
|
private val sessionObserver: SessionObserver,
|
||||||
|
) : SeenInvitesStoreFactory {
|
||||||
|
// We can have only one class accessing a single data store, so keep a cache of them.
|
||||||
|
private val cache = ConcurrentHashMap<SessionId, SeenInvitesStore>()
|
||||||
|
|
||||||
|
override fun getOrCreate(
|
||||||
|
sessionId: SessionId,
|
||||||
|
sessionCoroutineScope: CoroutineScope,
|
||||||
|
): SeenInvitesStore {
|
||||||
|
return cache.getOrPut(sessionId) {
|
||||||
|
DefaultSeenInvitesStore(
|
||||||
|
context = context,
|
||||||
|
sessionId = sessionId,
|
||||||
|
sessionCoroutineScope = sessionCoroutineScope,
|
||||||
|
sessionObserver = sessionObserver,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.invite.impl
|
||||||
|
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
|
interface SeenInvitesStoreFactory {
|
||||||
|
fun getOrCreate(
|
||||||
|
sessionId: SessionId,
|
||||||
|
sessionCoroutineScope: CoroutineScope,
|
||||||
|
): SeenInvitesStore
|
||||||
|
}
|
||||||
|
|
@ -10,14 +10,31 @@ package io.element.android.features.invite.impl.di
|
||||||
import com.squareup.anvil.annotations.ContributesTo
|
import com.squareup.anvil.annotations.ContributesTo
|
||||||
import dagger.Binds
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import io.element.android.features.invite.api.SeenInvitesStore
|
||||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||||
|
import io.element.android.features.invite.impl.SeenInvitesStoreFactory
|
||||||
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
|
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
||||||
@ContributesTo(SessionScope::class)
|
@ContributesTo(SessionScope::class)
|
||||||
@Module
|
@Module
|
||||||
interface InviteModule {
|
interface InviteModule {
|
||||||
@Binds
|
@Binds
|
||||||
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
|
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Provides
|
||||||
|
fun providesSeenInvitesStore(
|
||||||
|
factory: SeenInvitesStoreFactory,
|
||||||
|
matrixClient: MatrixClient,
|
||||||
|
): SeenInvitesStore {
|
||||||
|
return factory.getOrCreate(
|
||||||
|
matrixClient.sessionId,
|
||||||
|
matrixClient.sessionCoroutineScope,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue