Fix main-thread decryption block by caching token in StateFlow (#322)

Issue #278 reported that `AuthManager.getToken()` (which reads from
`EncryptedSharedPreferences`) was being called synchronously inside the
`MainNavigation` composable, causing UI thread blocks during navigation
transitions (PERF-10).

This commit resolves the issue by caching the token in a new `tokenFlow`
(`MutableStateFlow`) within `AuthManager`.
- The flow is initialised with the token during `AuthManager.init()`.
- Updates to the token (`setToken`, `setSelectedProfileId`,
  `setProfileToken`) are synchronised with `tokenFlow`.
- `MainNavigation` now correctly observes this state using
  `collectAsState()`, preventing disk-read blocking on recompositions.

Impact: Navigation transitions are now fully non-blocking. No side effects
are expected as state synchronisation maintains parity with persistent storage.

Resolves #278
This commit is contained in:
M57 authored and GitHub committed 2026-06-23 12:19:33 +03:00
1 parent 4d47db263f
commit 3798d0330f
2 files changed
+12 -1

No files matched your search

@@ -326,7 +326,8 @@ private fun appEntryProvider(
@Composable
fun MainNavigation(sessionId: String? = null) {
val hasToken = !AuthManager.getToken().isNullOrBlank()
val token by AuthManager.tokenFlow.collectAsState()
val hasToken = !token.isNullOrBlank()
val startScreen: NavKey = if (hasToken) ChatScreen else ConnectScreen
val backStack = rememberNavBackStack(startScreen)
@@ -57,6 +57,10 @@ object AuthManager {
private val _bottomNavDisplayModeFlow = MutableStateFlow<BottomNavDisplayMode>(BottomNavDisplayMode.ICON_AND_TEXT)
val bottomNavDisplayModeFlow: StateFlow<BottomNavDisplayMode> = _bottomNavDisplayModeFlow.asStateFlow()
private val _tokenFlow = MutableStateFlow<String?>(null)
val tokenFlow: StateFlow<String?> = _tokenFlow.asStateFlow()
private val gson = com.google.gson.Gson()
/**
@@ -84,6 +88,7 @@ object AuthManager {
_useDynamicColorsFlow.value = isUseDynamicColors()
_themePresetFlow.value = getThemePreset()
_bottomNavDisplayModeFlow.value = getBottomNavDisplayMode()
_tokenFlow.value = getToken()
}
}
@@ -122,6 +127,9 @@ object AuthManager {
token: String?,
) {
requirePrefs().edit().putString("token_$profileId", token).apply()
if (getSelectedProfileId() == profileId) {
_tokenFlow.value = token
}
}
fun getSelectedProfileId(): String? {
@@ -131,6 +139,7 @@ object AuthManager {
fun setSelectedProfileId(id: String?) {
requirePrefs().edit().putString(KEY_SELECTED_PROFILE_ID, id).apply()
_tokenFlow.value = getToken()
}
// ── Token ────────────────────────────────────────────────────────────
@@ -150,6 +159,7 @@ object AuthManager {
setProfileToken(selectedId, token)
} else {
requirePrefs().edit().putString(KEY_TOKEN, token).apply()
_tokenFlow.value = token
}
}