commonMain.com.slack.circuit.foundation.PausableState.kt Maven / Gradle / Ivy
The newest version!
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
@file:Suppress("NOTHING_TO_INLINE")
package com.slack.circuit.foundation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.remember
import com.slack.circuit.foundation.internal.withCompositionLocalProvider
import com.slack.circuit.retained.LocalRetainedStateRegistry
import com.slack.circuit.retained.RetainedStateRegistry
import com.slack.circuit.retained.rememberRetained
import com.slack.circuit.runtime.CircuitUiState
import com.slack.circuit.runtime.presenter.Presenter
/**
* By default [CircuitContent] will wrap presenters so that the last emitted [CircuitUiState] is
* replayed when the presenter is paused. If this behavior is not wanted, the [Presenter] should
* implement this interface to disable the behavior.
*/
@Stable public interface NonPausablePresenter : Presenter
/**
* Presents the UI state when the lifecycle is resumed, otherwise will replay the last emitted UI
* state.
*
* The [CircuitUiState] class returned by the [Presenter] is required to implement [equals] and
* [hashCode] methods correctly, otherwise this function can create composition loops.
*
* @param key A unique key for the pausable state
* @param isActive Whether the presenter is active or not.
*/
@Composable
public inline fun Presenter.presentWithLifecycle(
key: String? = null,
isActive: Boolean = LocalRecordLifecycle.current.isActive,
): UiState = pausableState(key, isActive) { present() }
/**
* Wraps a composable state producer, which will replay the last emitted state instance when
* [isActive] is `false`.
*
* The class [T] returned from [produceState] is required to implement [equals] and [hashCode]
* methods correctly, otherwise this function can create composition loops.
*
* @param key A unique key for the pausable state.
* @param isActive Whether the state producer should be active or not.
* @param produceState A composable lambda function which produces the state
*/
@Composable
public fun pausableState(
key: String? = null,
isActive: Boolean = LocalRecordLifecycle.current.isActive,
produceState: @Composable () -> T,
): T {
// This is explicitly not snapshot (or state) backed as don't want it to influence composition.
// It is here to only store the last emitted state value. Only the caller, or the produceState
// should be triggering recompositions.
val state = remember(key) { MutableRef(null) }
val saveableStateHolder = rememberSaveableStateHolderWithReturn()
return if (isActive || state.value == null) {
val retainedStateRegistry = rememberRetained(key = key) { RetainedStateRegistry() }
withCompositionLocalProvider(LocalRetainedStateRegistry provides retainedStateRegistry) {
saveableStateHolder.SaveableStateProvider(
key = key ?: "pausable_state",
content = produceState,
)
}
.also {
// Store the last emitted state
state.value = it
}
} else {
// Else, we just emit the last stored state instance
state.value!!
}
}
internal data class MutableRef(var value: R?)