Skip to content

Conversation

@tajchert
Copy link

@tajchert tajchert commented Dec 7, 2025

Disclaimer: This PR was created with AI assisted agent (Claude Code) but manually checked myself to my best knowledge and this repository standards. If you feel this is against contribution guidelines (I haven't found anything related to such) feel free to disregard this PR. I honestly believe this is valid PR, and I tested it manually as well.

This fix OTP code freeze when returning to Authenticator app from background - issue #6244
when occasionally user can experience frozen TOTP codes and countdown timers when returning to the Authenticator app from background. The codes remain static and don't update until the screen is manually refreshed. This changes flow implementation in AUthenticator app (Flow) to one that is Password Manager (StateFlow).

Root Cause

The bug stems from a flow lifecycle mismatch in TotpCodeManagerImpl. The TOTP code update mechanism relies on continuous Flow emissions with a 1-second delay loop, but this flow stops when the app goes to background.

The race condition:

  1. App goes to background → collectAsStateWithLifecycle() stops collecting
  2. After 5 seconds → SharingStarted.WhileSubscribed(5_000L) stops upstream flows
  3. The delay(ONE_SECOND_MILLISECOND) timer loop stops entirely
  4. On return to foreground:
    • Collection resumes, triggering new subscription
    • Cold Flow is recreated from scratch for each item
    • Stale cached stateIn value briefly displayed before new emissions
    • Multiple items compound this as combine() waits for all flows to emit

Solution

Aligned TotpCodeManagerImpl with the Password Manager's proven pattern:

  1. Return StateFlow instead of cold Flow — Maintains current state, subscribers get immediate value
  2. Per-item StateFlow caching via mutableMapOf<AuthenticatorItem, StateFlow<...>> — Prevents flow recreation on resubscribe
  3. Per-item CoroutineScope — Each TOTP timer runs independently with proper lifecycle
  4. Removed 5-second stop timeout — Uses SharingStarted.WhileSubscribed() without delay
  5. Use SharingStarted.Eagerly for per-item flows — Ensures cached per-item flows continue emitting even when the combined flow changes (e.g., when adding new items)
  6. Explicit cleanup via onCompletion — Removes flows from cache and cancels scope when no longer needed

Changes

  • TotpCodeManager.kt — Changed return type from Flow<List<VerificationCodeItem>> to StateFlow<List<VerificationCodeItem>>
  • TotpCodeManagerImpl.kt — Added DispatcherManager dependency, per-item StateFlow caching with getOrCreateItemStateFlow(), and cleanup handlers
  • AuthenticatorManagerModule.kt — Passes dispatcherManager to TotpCodeManagerImpl constructor
  • AuthenticatorRepositoryImpl.kt — Removed 5-second timeout, now uses SharingStarted.WhileSubscribed() without delay
  • Test files — Updated mocks to use MutableStateFlow() instead of flowOf()

Testing

  • All existing unit tests pass
  • Manual testing verified:
    • Background app for 5+ seconds, return ✓
    • Background during code expiration, return ✓
    • Rotate device while showing codes ✓
    • Rapid app switching ✓

I wasn't able to recreate issue after this fix.

@CLAassistant
Copy link

CLAassistant commented Dec 7, 2025

CLA assistant check
All committers have signed the CLA.

@bitwarden-bot
Copy link

Thank you for your contribution! We've added this to our internal tracking system for review.
ID: PM-29309
Link: https://bitwarden.atlassian.net/browse/PM-29309

Details on our contribution process can be found here: https://contributing.bitwarden.com/contributing/pull-requests/community-pr-process.

@bitwarden-bot bitwarden-bot changed the title [BWA-209] Fix TOTP countdown freeze when returning to Authenticator app (change Flow to StateFlow) [PM-29309] [BWA-209] Fix TOTP countdown freeze when returning to Authenticator app (change Flow to StateFlow) Dec 7, 2025
Refactor TotpCodeManager to use per-item StateFlow caching, matching the
Password Manager's pattern. This prevents flow recreation on resubscribe
and removes the 5-second stop timeout that caused stale state when
returning from background.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants