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