State Management
Global State Management
The global state is specified by the State type of the application.
It's simple and straightforward.
There's currently no real global state management solution (yet).
Signals
So far, we've seen "magic" signals do most of the work for us in our application.
Signals are a way to store values that can be observed and updated within the UI.
They are similar to flutter hooks or react signals (if you are familiar with those).
Every signal must implement the Signal trait.
It contains functions, such as...
get() -> Ref<'_, T>to get a reference to the inner value.set(value: T)to set the value of the signal.listen(listener: Listener)to listen to changes to the signal value.
There are more methods, but these are the ones we will focus on for now.
The get method simply returns a reference to the inner value.
It can only return a reference, as the inner value cannot be safely mutated while the signal is being used.
The set method sets the inner value of the signal in a safe manner and notifies the signal listeners of changes.
The listen method allows you to listen to changes to the signal value.
Worth mentioning is also the hook method, which hooks the signal to the application lifecycle.
Using AppContext::use_signal() will hook the signal automatically to the application lifecycle.
Behind the scenes
Basic signals like StateSignal (which just contains a mutable value) are based on Rc and RefCell from the standard
Rust library. This is because we need to be able to mutate the inner value without it being in use. See
the std::cell docs from the standard library for more.
The hook method will add a listener to the signal that will trigger a re-evaluation of the UI, once the signal value changes.
Some signals also don't make use of listeners and state change at all, because they contain any state that can be
mutated.
EvalSignal for example produces a value by calling a closure and doesn't need to notify listeners of changes, because
it doesn't contain any inner state (except for the closure).
Basic Signals
Here are some basic built-in signals:
| Signal | Shortcut | Function | Example Usage |
|---|---|---|---|
StateSignal<T> | context.use_state(...) | Stores mutable data that can be set or directly mutated | Counter context.use_state(1) |
FixedSignal<T> | context.use_fixed(...) | Contains a fixed/immutable value. | Fixed values context.use_fixed("Fixed String".to_string()) |
EvalSignal<T> | context.use_eval(...) | Produces its inner value by calling a closure | Button-Press-Actions context.use_eval(\|\| Update::DRAW) |
MemoizedSignal<T> | context.use_memoized(...) | Caches its inner value once via a closure and returns the cached value if requested again | Using complex and expensive immutable values context.use_memoized(\|\| Database::new()) |
MapSignal<T, U> | signal.map(...) | Maps a signal value of type T to a value of type U | Mapping signal types signal.map(\|int\| int.to_string()) |
MaybeSignal<T>
Most of the time, you want to use cheap fixed and immutable data (like a simple constant text), but sometimes you want reactive data (using a signal).
These two types of data can be combined using the MaybeSignal which may be a signal, but can also be a fixed value.
MaybeSignal is an enum composed of two different variants:
MaybeSignal::Signal(BoxedSignal<T>)- A generic signalMaybeSignal::Fixed(T)- A fixed value
Signals automatically implement Into<MaybeSignal<T>> and have the maybe() -> MaybeSignal<T> method.
Widgets will take impl Into<MaybeSignal<T>>> parameters, so you don't necessarily need to use MaybeSignal,
but it's useful to know about.