Skip to main content

cranpose_core/
hooks.rs

1use std::{hash::Hash, rc::Rc, sync::Arc};
2
3use crate::{
4    composer_context,
5    owned::Owned,
6    runtime,
7    state::{
8        DerivedState, MutableState, OwnedMutableState, SnapshotStateList, SnapshotStateMap, State,
9        StructuralEqual,
10    },
11};
12
13#[track_caller]
14pub fn remember<T: 'static>(init: impl FnOnce() -> T) -> Owned<T> {
15    let source = crate::caller_location_key();
16    composer_context::with_composer(|composer| composer.remember_at(source, init))
17}
18
19/// Like [`remember`], but recomputes whenever `key` changes.
20///
21/// A plain `remember` is keyed only by composition position: when a slot is
22/// reused with different inputs (a list row that changes identity, an icon
23/// whose path prop changes), the stale value survives. `rememberKeyed`
24/// stores the key beside the value and re-runs `init` on mismatch — the JC
25/// `remember(key1) { ... }` contract.
26#[allow(non_snake_case)]
27#[track_caller]
28pub fn rememberKeyed<K, T>(key: K, init: impl FnOnce(&K) -> T) -> T
29where
30    K: PartialEq + 'static,
31    T: Clone + 'static,
32{
33    let slot = remember(|| std::cell::RefCell::new(None::<(K, T)>));
34    slot.with(|cell| {
35        let mut stored = cell.borrow_mut();
36        match &*stored {
37            Some((stored_key, value)) if *stored_key == key => value.clone(),
38            _ => {
39                let value = init(&key);
40                *stored = Some((key, value.clone()));
41                value
42            }
43        }
44    })
45}
46
47/// Returns a [`MutableState`] that always holds the latest value.
48///
49/// The state **reference** is stable across recompositions; only the **value** updates.
50/// This allows closures to capture a stable reference while reading fresh values.
51///
52/// # Use Case
53/// Use when a `remember`ed closure needs to read a value that changes each recomposition
54/// without recreating the closure itself.
55///
56/// # Example
57/// ```rust,ignore
58/// let config = build_config(); // Rebuilt each recomposition
59/// let config_state = rememberUpdatedState(config);
60///
61/// // This closure is created once, reads latest config via state
62/// let callback = remember(|| {
63///     let cfg = config_state;
64///     Rc::new(move || do_something(&cfg.value()))
65/// }).with(|c| c.clone());
66/// ```
67///
68/// # JC Equivalent
69/// ```kotlin
70/// @Composable
71/// fun <T> rememberUpdatedState(newValue: T): State<T> =
72///     remember { mutableStateOf(newValue) }.apply { value = newValue }
73/// ```
74#[allow(non_snake_case)]
75#[track_caller]
76pub fn rememberUpdatedState<T: Clone + 'static>(value: T) -> MutableState<T> {
77    let source = crate::caller_location_key();
78    composer_context::with_composer(|composer| {
79        let runtime = composer.runtime_handle();
80        let state = composer.remember_at(source, || {
81            OwnedMutableState::with_runtime(value.clone(), runtime)
82        });
83        state.with(|s| {
84            s.set(value);
85            s.handle()
86        })
87    })
88}
89
90#[cfg(feature = "internal")]
91#[allow(non_snake_case)]
92pub fn withFrameNanos(
93    callback: impl FnOnce(u64) + 'static,
94) -> crate::internal::FrameCallbackRegistration {
95    composer_context::with_composer(|composer| {
96        composer
97            .runtime_handle()
98            .frame_clock()
99            .with_frame_nanos(callback)
100    })
101}
102
103#[cfg(feature = "internal")]
104#[allow(non_snake_case)]
105pub fn withFrameMillis(
106    callback: impl FnOnce(u64) + 'static,
107) -> crate::internal::FrameCallbackRegistration {
108    composer_context::with_composer(|composer| {
109        composer
110            .runtime_handle()
111            .frame_clock()
112            .with_frame_millis(callback)
113    })
114}
115
116/// Creates a new `MutableState` initialized with the given value.
117///
118/// `MutableState` is a cheap copyable observable handle. Reads are tracked by the
119/// current composer or snapshot, and writes trigger recomposition of scopes that
120/// read it.
121///
122/// Writing a value equal to the one already there is not a change, so it does
123/// not recompose anything -- Jetpack Compose's `structuralEqualityPolicy()`,
124/// which is its default too. Use [`mutableStateOfNeverEqual`] for a value that
125/// cannot be compared, or one whose every write must count.
126///
127/// # When to use
128/// Use `mutableStateOf` when:
129/// 1.  You are creating state properties inside a struct or class (not a composable function).
130/// 2.  You are implementing a custom state management solution.
131///
132/// **Inside a `#[composable]` function this must sit inside a `remember`**,
133/// either [`rememberMutableStateOf`] for a single state or [`remember`] around
134/// the struct that holds several. Called straight from a composable body it
135/// makes a new state every pass and loses the value, exactly as in Kotlin.
136///
137/// # Example
138///
139/// ```rust,ignore
140/// struct MyViewModel {
141///     name: MutableState<String>,
142/// }
143///
144/// impl MyViewModel {
145///     fn new() -> Self {
146///         Self {
147///             name: mutableStateOf(String::from("Alice")),
148///         }
149///     }
150/// }
151///
152/// #[composable]
153/// fn rememberMyViewModel() -> MyViewModel {
154///     remember(MyViewModel::new).with(|model| model.clone())
155/// }
156/// ```
157///
158/// A state made while a [`remember`] is building its value belongs to that
159/// slot and is released with it, which is what lets the struct above be
160/// remembered whole. Made anywhere else it is owned by the runtime and lives
161/// as long as the runtime does; to tie that to a Rust owner instead, store an
162/// [`OwnedMutableState`] or call [`MutableState::retain`] on the handle.
163#[allow(non_snake_case)]
164pub fn mutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> MutableState<T> {
165    current_runtime("mutableStateOf")
166        .alloc_persistent_state_with_policy(initial, Arc::new(StructuralEqual))
167}
168
169/// Like [`mutableStateOf`], for a value that cannot be compared or whose every
170/// write must count as a change.
171///
172/// This is Jetpack Compose's `neverEqualPolicy()`, and the non-remembered form
173/// of [`rememberMutableStateOfNeverEqual`].
174#[allow(non_snake_case)]
175pub fn mutableStateOfNeverEqual<T: Clone + 'static>(initial: T) -> MutableState<T> {
176    current_runtime("mutableStateOfNeverEqual").alloc_persistent_state(initial)
177}
178
179#[allow(non_snake_case)]
180pub fn ownedMutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> OwnedMutableState<T> {
181    OwnedMutableState::with_runtime_structural_eq(initial, current_runtime("ownedMutableStateOf"))
182}
183
184/// Like [`ownedMutableStateOf`], for a value that cannot be compared.
185#[allow(non_snake_case)]
186pub fn ownedMutableStateOfNeverEqual<T: Clone + 'static>(initial: T) -> OwnedMutableState<T> {
187    OwnedMutableState::with_runtime(initial, current_runtime("ownedMutableStateOfNeverEqual"))
188}
189
190fn current_runtime(what: &str) -> runtime::RuntimeHandle {
191    composer_context::try_with_composer(|composer| composer.runtime_handle())
192        .or_else(runtime::current_runtime_handle)
193        .unwrap_or_else(|| {
194            panic!(
195                "{what} requires an active runtime. Create state inside a composition or after a Runtime is created."
196            )
197        })
198}
199
200/// Like [`mutableStateOf`] but returns `None` if no runtime is available.
201///
202/// Use this when you want to lazily initialize reactive state and gracefully
203/// handle the case where the runtime isn't yet available.
204#[allow(non_snake_case)]
205pub fn try_mutableStateOf<T: Clone + PartialEq + 'static>(initial: T) -> Option<MutableState<T>> {
206    let runtime = composer_context::try_with_composer(|composer| composer.runtime_handle())
207        .or_else(runtime::current_runtime_handle)?;
208    Some(runtime.alloc_persistent_state_with_policy(initial, Arc::new(StructuralEqual)))
209}
210
211#[allow(non_snake_case)]
212pub fn mutableStateListOf<T, I>(values: I) -> SnapshotStateList<T>
213where
214    T: Clone + 'static,
215    I: IntoIterator<Item = T>,
216{
217    composer_context::with_composer(move |composer| composer.mutable_state_list_of(values))
218}
219
220#[allow(non_snake_case)]
221pub fn mutableStateList<T: Clone + 'static>() -> SnapshotStateList<T> {
222    mutableStateListOf(std::iter::empty::<T>())
223}
224
225#[allow(non_snake_case)]
226pub fn mutableStateMapOf<K, V, I>(pairs: I) -> SnapshotStateMap<K, V>
227where
228    K: Clone + Eq + Hash + 'static,
229    V: Clone + 'static,
230    I: IntoIterator<Item = (K, V)>,
231{
232    composer_context::with_composer(move |composer| composer.mutable_state_map_of(pairs))
233}
234
235#[allow(non_snake_case)]
236pub fn mutableStateMap<K, V>() -> SnapshotStateMap<K, V>
237where
238    K: Clone + Eq + Hash + 'static,
239    V: Clone + 'static,
240{
241    mutableStateMapOf(std::iter::empty::<(K, V)>())
242}
243
244/// A composable hook that creates and remembers a `MutableState`.
245///
246/// This is the primary way to define local state in a composable function.
247/// It combines `remember` and `mutableStateOf`.
248///
249/// # Arguments
250///
251/// * `init` - A closure that provides the initial value. This is only called once
252///   when the composable enters the composition.
253///
254/// # Example
255///
256/// ```rust,ignore
257/// #[composable]
258/// fn Counter() {
259///     // "count" persists across recompositions.
260///     // If we used mutableStateOf directly, it would reset to 0 every frame.
261///     let count = rememberMutableStateOf(|| 0);
262///
263///     Button(
264///         Modifier::empty(),
265///         ButtonSpec::default(),
266///         move || count.set(count.value() + 1),
267///         || Text(format!("Count: {}", count.value()))
268///     );
269/// }
270/// ```
271#[allow(non_snake_case)]
272#[track_caller]
273pub fn rememberMutableStateOf<T: Clone + PartialEq + 'static>(
274    init: impl FnOnce() -> T,
275) -> MutableState<T> {
276    let source = crate::caller_location_key();
277    composer_context::with_composer(|composer| {
278        let runtime = composer.runtime_handle();
279        composer
280            .remember_at(source, || {
281                OwnedMutableState::with_runtime_structural_eq(init(), runtime)
282            })
283            .with(|state| state.handle())
284    })
285}
286
287#[allow(non_snake_case)]
288#[track_caller]
289pub fn rememberMutableStateOfNeverEqual<T: Clone + 'static>(
290    init: impl FnOnce() -> T,
291) -> MutableState<T> {
292    let source = crate::caller_location_key();
293    composer_context::with_composer(|composer| {
294        let runtime = composer.runtime_handle();
295        composer
296            .remember_at(source, || OwnedMutableState::with_runtime(init(), runtime))
297            .with(|state| state.handle())
298    })
299}
300
301#[allow(non_snake_case)]
302#[track_caller]
303pub fn derivedStateOf<T: 'static + Clone>(compute: impl Fn() -> T + 'static) -> State<T> {
304    let source = crate::caller_location_key();
305    composer_context::with_composer(|composer| {
306        composer.with_group(source, |composer| {
307            let should_recompute = composer
308                .current_recompose_scope()
309                .map(|scope| scope.should_recompose())
310                .unwrap_or(true);
311            let runtime = composer.runtime_handle();
312            let compute_rc: Rc<dyn Fn() -> T> = Rc::new(compute);
313            let derived = composer.remember_at(source, || {
314                DerivedState::new(runtime.clone(), compute_rc.clone())
315            });
316            derived.update(|derived| {
317                derived.set_compute(compute_rc.clone());
318                if should_recompute {
319                    derived.recompute();
320                }
321            });
322            derived.with(|derived| derived.state.as_state())
323        })
324    })
325}