mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Avoid database access on the main thread when opening the settings
`getUserProfile()` was storing the user profile (DB read, avatar Base64 encoding, DB write) on the caller dispatcher, i.e. the main thread when called from a presenter, causing glitches in the screen transition. - Run the whole `getUserProfile()` on the session dispatcher. - Make `DatabaseSessionStore` main-safe by running the queries on the IO dispatcher. - Do not refresh the profile twice from the settings screen. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+1
-4
@@ -40,10 +40,7 @@ class PreferencesAccountPresenter(
|
||||
@Composable
|
||||
override fun present(): PreferencesAccountState {
|
||||
val matrixUser = matrixClient.userProfile.collectAsState()
|
||||
LaunchedEffect(Unit) {
|
||||
// Force a refresh of the profile
|
||||
matrixClient.getUserProfile()
|
||||
}
|
||||
// Note: no need to force a refresh of the profile here, PreferencesRootPresenter, which embeds this presenter, already does it.
|
||||
val showLinkNewDevice by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.QrCodeLogin)
|
||||
}.collectAsState(initial = false)
|
||||
|
||||
+8
-6
@@ -519,12 +519,14 @@ class RustMatrixClient(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getUserProfile(): Result<MatrixUser> = getProfile(sessionId)
|
||||
.onSuccess { matrixUser ->
|
||||
// Also update our session storage
|
||||
_userProfile.emit(matrixUser)
|
||||
storeUserProfile(matrixUser)
|
||||
}
|
||||
override suspend fun getUserProfile(): Result<MatrixUser> = withContext(sessionDispatcher) {
|
||||
getProfile(sessionId)
|
||||
.onSuccess { matrixUser ->
|
||||
// Also update our session storage
|
||||
_userProfile.emit(matrixUser)
|
||||
storeUserProfile(matrixUser)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the user profile in the session storage, including a base64 encoded thumbnail of the avatar,
|
||||
|
||||
+84
-73
@@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@@ -51,85 +52,93 @@ class DatabaseSessionStore(
|
||||
}
|
||||
|
||||
override suspend fun addSession(sessionData: SessionData) {
|
||||
sessionDataMutex.withLock {
|
||||
val lastUsageIndex = getLastUsageIndex()
|
||||
database.sessionDataQueries.insertSessionData(
|
||||
sessionData
|
||||
.copy(
|
||||
// position value does not really matter, so just use lastUsageIndex + 1 to ensure that
|
||||
// the value is always greater than value of any existing account
|
||||
position = lastUsageIndex + 1,
|
||||
lastUsageIndex = lastUsageIndex + 1,
|
||||
)
|
||||
.toDbModel()
|
||||
)
|
||||
withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
val lastUsageIndex = getLastUsageIndex()
|
||||
database.sessionDataQueries.insertSessionData(
|
||||
sessionData
|
||||
.copy(
|
||||
// position value does not really matter, so just use lastUsageIndex + 1 to ensure that
|
||||
// the value is always greater than value of any existing account
|
||||
position = lastUsageIndex + 1,
|
||||
lastUsageIndex = lastUsageIndex + 1,
|
||||
)
|
||||
.toDbModel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateData(sessionData: SessionData) {
|
||||
sessionDataMutex.withLock {
|
||||
val result = database.sessionDataQueries.selectByUserId(sessionData.userId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
val result = database.sessionDataQueries.selectByUserId(sessionData.userId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
|
||||
if (result == null) {
|
||||
Timber.e("User ${sessionData.userId} not found in session database")
|
||||
return
|
||||
if (result == null) {
|
||||
Timber.e("User ${sessionData.userId} not found in session database")
|
||||
return@withContext
|
||||
}
|
||||
// Copy new data from SDK, but keep application data
|
||||
database.sessionDataQueries.updateSession(
|
||||
sessionData.copy(
|
||||
loginTimestamp = result.loginTimestamp,
|
||||
position = result.position,
|
||||
lastUsageIndex = result.lastUsageIndex,
|
||||
userDisplayName = result.userDisplayName,
|
||||
userAvatarUrl = result.userAvatarUrl,
|
||||
userAvatarData = result.userAvatarData,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
// Copy new data from SDK, but keep application data
|
||||
database.sessionDataQueries.updateSession(
|
||||
sessionData.copy(
|
||||
loginTimestamp = result.loginTimestamp,
|
||||
position = result.position,
|
||||
lastUsageIndex = result.lastUsageIndex,
|
||||
userDisplayName = result.userDisplayName,
|
||||
userAvatarUrl = result.userAvatarUrl,
|
||||
userAvatarData = result.userAvatarData,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateUserProfile(sessionId: String, displayName: String?, avatarUrl: String?, avatarData: String?) {
|
||||
sessionDataMutex.withLock {
|
||||
withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
val result = database.sessionDataQueries.selectByUserId(sessionId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
if (result == null) {
|
||||
Timber.e("User $sessionId not found in session database")
|
||||
return@withContext
|
||||
}
|
||||
database.sessionDataQueries.updateSession(
|
||||
result.copy(
|
||||
userDisplayName = displayName,
|
||||
userAvatarUrl = avatarUrl,
|
||||
userAvatarData = avatarData,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setLatestSession(sessionId: String) {
|
||||
withContext(dispatchers.io) {
|
||||
val latestSession = getLatestSession()
|
||||
if (latestSession?.userId == sessionId) {
|
||||
// Already the latest session
|
||||
return@withContext
|
||||
}
|
||||
val lastUsageIndex = latestSession?.lastUsageIndex ?: 0
|
||||
val result = database.sessionDataQueries.selectByUserId(sessionId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
if (result == null) {
|
||||
Timber.e("User $sessionId not found in session database")
|
||||
return
|
||||
return@withContext
|
||||
}
|
||||
sessionDataMutex.withLock {
|
||||
// Update lastUsageIndex of the session
|
||||
database.sessionDataQueries.updateSession(
|
||||
result.copy(
|
||||
lastUsageIndex = lastUsageIndex + 1,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
database.sessionDataQueries.updateSession(
|
||||
result.copy(
|
||||
userDisplayName = displayName,
|
||||
userAvatarUrl = avatarUrl,
|
||||
userAvatarData = avatarData,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setLatestSession(sessionId: String) {
|
||||
val latestSession = getLatestSession()
|
||||
if (latestSession?.userId == sessionId) {
|
||||
// Already the latest session
|
||||
return
|
||||
}
|
||||
val lastUsageIndex = latestSession?.lastUsageIndex ?: 0
|
||||
val result = database.sessionDataQueries.selectByUserId(sessionId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
if (result == null) {
|
||||
Timber.e("User $sessionId not found in session database")
|
||||
return
|
||||
}
|
||||
sessionDataMutex.withLock {
|
||||
// Update lastUsageIndex of the session
|
||||
database.sessionDataQueries.updateSession(
|
||||
result.copy(
|
||||
lastUsageIndex = lastUsageIndex + 1,
|
||||
).toDbModel()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,32 +149,32 @@ class DatabaseSessionStore(
|
||||
?: -1L
|
||||
}
|
||||
|
||||
override suspend fun getLatestSession(): SessionData? {
|
||||
return sessionDataMutex.withLock {
|
||||
override suspend fun getLatestSession(): SessionData? = withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.selectLatest()
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getSession(sessionId: String): SessionData? {
|
||||
return sessionDataMutex.withLock {
|
||||
override suspend fun getSession(sessionId: String): SessionData? = withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.selectByUserId(sessionId)
|
||||
.executeAsOneOrNull()
|
||||
?.toApiModel()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAllSessions(): List<SessionData> {
|
||||
return sessionDataMutex.withLock {
|
||||
override suspend fun getAllSessions(): List<SessionData> = withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.selectAll()
|
||||
.executeAsList()
|
||||
.map { it.toApiModel() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun numberOfSessions(): Int {
|
||||
return sessionDataMutex.withLock {
|
||||
override suspend fun numberOfSessions(): Int = withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.count()
|
||||
.executeAsOneOrNull()
|
||||
?.toInt()
|
||||
@@ -181,8 +190,10 @@ class DatabaseSessionStore(
|
||||
}
|
||||
|
||||
override suspend fun removeSession(sessionId: String) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.removeSession(sessionId)
|
||||
withContext(dispatchers.io) {
|
||||
sessionDataMutex.withLock {
|
||||
database.sessionDataQueries.removeSession(sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user