diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/util/StateFlowExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/util/StateFlowExtensions.kt index 4708035c58..f15ec64b28 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/util/StateFlowExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/repository/util/StateFlowExtensions.kt @@ -14,10 +14,36 @@ import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map /** - * Invokes the [observer] callback whenever the user is logged in, the active changes, and there - * are subscribers to the [MutableStateFlow]. The flow from all previous calls to the `observer` - * is canceled whenever the `observer` is re-invoked, there is no active user (logged-out), or - * there are no subscribers to the [MutableStateFlow]. + * Lazily invokes the [observer] callback with the active user's ID only when this MutableStateFlow + * has external collectors and a user is logged in. Designed for operations that should only run + * when UI actively observes the resulting data, but do not require the vault to be unlocked. + * + * **Active User Tracking:** + * This function specifically tracks the active user from [userStateFlow]. When the active user + * changes (e.g., account switching), the previous observer flow is canceled and a new one is + * started for the new active user. + * + * **Subscription Detection:** + * Uses [MutableStateFlow.subscriptionCount] to detect external collectors. Only external + * `.collect()` calls increment subscriptionCount—internal flow operations (map, flatMapLatest, + * update, etc.) do not affect it. + * + * **Common Pattern:** + * ```kotlin + * private val _triggerFlow = MutableStateFlow(Unit) + * val dataFlow = _triggerFlow + * .observeWhenSubscribedAndLoggedIn(userFlow) { activeUserId -> + * repository.getData(activeUserId) // Only runs when dataFlow is collected + * } + * // _triggerFlow.update {} does NOT affect subscriptionCount + * ``` + * + * **Observer Lifecycle:** + * - **Invoked** when subscriptionCount > 0 and a user is logged in + * - **Re-invoked** when the active user changes (account switch) + * - **Canceled** when subscribers disconnect or user logs out + * + * @see observeWhenSubscribedAndUnlocked for variant that also requires vault to be unlocked */ @OptIn(ExperimentalCoroutinesApi::class) fun MutableStateFlow.observeWhenSubscribedAndLoggedIn( @@ -35,11 +61,36 @@ fun MutableStateFlow.observeWhenSubscribedAndLoggedIn( } /** - * Invokes the [observer] callback whenever the user is logged in, the active changes, - * the vault for the user changes and there are subscribers to the [MutableStateFlow]. - * The flow from all previous calls to the `observer` - * is canceled whenever the `observer` is re-invoked, there is no active user (logged-out), - * there are no subscribers to the [MutableStateFlow] or the vault is not unlocked. + * Lazily invokes the [observer] callback with the active user's ID only when this MutableStateFlow + * has external collectors, a user is logged in, and the active user's vault is unlocked. Designed + * for expensive operations that should only run when UI actively observes the resulting data. + * + * **Active User Tracking:** + * This function specifically tracks the active user from [userStateFlow]. When the active user + * changes (e.g., account switching), the previous observer flow is canceled and a new one is + * started for the new active user. The vault unlock state is also tracked per-user. + * + * **Subscription Detection:** + * Uses [MutableStateFlow.subscriptionCount] to detect external collectors. Only external + * `.collect()` calls increment subscriptionCount—internal flow operations (map, flatMapLatest, + * update, etc.) do not affect it. + * + * **Common Pattern:** + * ```kotlin + * private val _triggerFlow = MutableStateFlow(Unit) + * val dataFlow = _triggerFlow + * .observeWhenSubscribedAndUnlocked(userFlow, unlockFlow) { activeUserId -> + * repository.getExpensiveData(activeUserId) // Only runs when dataFlow is collected + * } + * // _triggerFlow.update {} does NOT affect subscriptionCount + * ``` + * + * **Observer Lifecycle:** + * - **Invoked** when subscriptionCount > 0, a user is logged in, and active user's vault unlocked + * - **Re-invoked** when the active user changes (account switch) or vault state changes + * - **Canceled** when subscribers disconnect, user logs out, or vault locks + * + * @see observeWhenSubscribedAndLoggedIn for variant without vault unlock requirement */ @OptIn(ExperimentalCoroutinesApi::class) fun MutableStateFlow.observeWhenSubscribedAndUnlocked(