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:
@@ -12,36 +12,25 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringSetPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.invite.api.SeenInvitesStore
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
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.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.SessionObserver
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import javax.inject.Inject
|
||||
|
||||
private val seenInvitesKey = stringSetPreferencesKey("seenInvites")
|
||||
|
||||
@SingleIn(SessionScope::class)
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultSeenInvitesStore @Inject constructor(
|
||||
@ApplicationContext context: Context,
|
||||
currentSessionIdHolder: CurrentSessionIdHolder,
|
||||
@SessionCoroutineScope sessionCoroutineScope: CoroutineScope,
|
||||
class DefaultSeenInvitesStore(
|
||||
context: Context,
|
||||
sessionId: SessionId,
|
||||
sessionCoroutineScope: CoroutineScope,
|
||||
sessionObserver: SessionObserver,
|
||||
) : SeenInvitesStore {
|
||||
private val sessionId: SessionId = currentSessionIdHolder.current
|
||||
|
||||
init {
|
||||
sessionObserver.addListener(object : SessionListener {
|
||||
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 dagger.Binds
|
||||
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.impl.SeenInvitesStoreFactory
|
||||
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
|
||||
@ContributesTo(SessionScope::class)
|
||||
@Module
|
||||
interface InviteModule {
|
||||
@Binds
|
||||
fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter<AcceptDeclineInviteState>
|
||||
|
||||
companion object {
|
||||
@Provides
|
||||
fun providesSeenInvitesStore(
|
||||
factory: SeenInvitesStoreFactory,
|
||||
matrixClient: MatrixClient,
|
||||
): SeenInvitesStore {
|
||||
return factory.getOrCreate(
|
||||
matrixClient.sessionId,
|
||||
matrixClient.sessionCoroutineScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user