Skip to main content

cranpose_core/
hooks.rs

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