Persistent preferences
day::prefs stores small string values between launches, such as a selected theme or volume.
It is provided by day-part-prefs and re-exported by day through the default-enabled prefs
feature. Apps using the facade do not need a separate dependency.
Use bind to restore a signal’s saved value and persist later changes. For files, use
app-local file storage. Neither API is an encrypted store for passwords or tokens.
A direct day-part-prefs dependency remains available. The day::prefs and day_part_prefs
paths expose the same API. Disabling the facade’s default features removes its preferences
dependency; enable any other default features your app still needs explicitly.
Authoring
day::prefs::set("greeting", "hello"); // persist a value
assert_eq!(day::prefs::get("greeting").as_deref(), Some("hello"));
assert!(day::prefs::contains("greeting"));
day::prefs::remove("greeting"); // delete it
| Function | Behavior |
|---|---|
set(key, value) -> bool | Persist value under key, overwriting. true when the write committed. |
get(key) -> Option<String> | The stored string, or None if absent. A stored "" is Some(""). |
remove(key) -> bool | Delete the value; true only if it existed and was removed. |
contains(key) -> bool | Whether a value is currently stored under key. |
bind(key, signal) | Two-way-bind a Signal<T> to a stored value: seed from the store now, persist every later change. T round-trips through FromStr/ToString. Call it right after creating the signal; the write-back stops with the creating scope. |
bind_json(key, store) | (day::prefs, with the model feature.) Keep a keyed day-model store (Store<Keyed<T>>, T: Serialize + DeserializeOwned) under key as one JSON array: seed it from the saved rows now, then save every later change, a field edit as well as an insert, removal or move. An unreadable value leaves the store as it is and logs a warning. Suits a small list; larger documents belong in day-persistence. |
install_nav_store() | Install this store as day-core’s navigation-persistence sink, so a nav/nav_stack marked .restore(key) remembers its state across launches (and an Android process death). day::launch calls it when the app has installed no store of its own, so an app calls it only when it boots without the facade. Nav keys are namespaced under day.nav.. See navigation. |
Values persist across launches. Platform selection is purely #[cfg(target_os)] (plus
#[cfg(target_arch = "wasm32")] for the web), since persistence depends on the OS, not on which
widget toolkit is in use. The one cargo feature, memory, swaps every platform store for an
in-process map per thread; day’s headless mock backend turns it on, so an app’s cargo test
starts with empty preferences and never touches the user’s real ones. A test that restarts its
own process to check persistence gives the children DAY_PREFS_DIR=<dir>: the file store in that
directory then serves in place of the map, private to that test. The file store (Linux, Windows,
HarmonyOS) honors DAY_PREFS_DIR in every build. parts/day-part-prefs/examples/prefs.rs is a plain main that uses it with no
Day framework at all (run it twice to watch a value survive the process).
This is a small string store for user settings and lightweight app state, not a database. Keep values modest; large blobs belong in a file.
Per-platform native realization
| OS | API | dependency |
|---|---|---|
| macOS | NSUserDefaults.standard | objc2 + objc2-foundation, shared apple.rs |
| iOS | NSUserDefaults.standard | objc2 + objc2-foundation, shared apple.rs |
| Android | SharedPreferences (MODE_PRIVATE) via a Java shim | day-android + [package.metadata.day.android] |
| Linux | per-app file store under $XDG_CONFIG_HOME/day (or ~/.config/day) | std only, shared file.rs |
| Windows | per-app file store under %APPDATA%\day | std only, shared file.rs |
| HarmonyOS | file store, best-effort in the app sandbox (target_env = "ohos") | std only, shared file.rs |
| Web | localStorage (per origin, day.pref. namespace) via the day-dom shim | the web-dom host page (docs/web.md), web.rs |
What each platform does
- macOS / iOS:
NSUserDefaults.standardis the system’s per-application preferences store (a plist under~/Library/Preferenceson macOS, the app container on iOS). It is toolkit-independent (noNSApplication/UIApplication, run loop, or window), so the crate works inday-qtbinaries and plaincargo testprocesses as well as underday-appkit/day-uikit.setObject:forKey:is the onlyunsafeobjc2 call (the value must be a property-list type, and we always pass a realNSString); writes are immediately readable and flushed to disk by the system.setalways returnstrue. - Android: an app-private
SharedPreferencesfile namedday_part_prefs, opened withContext.MODE_PRIVATEfromday-android’s cachedContext(DayBridge.ctx). Writes useEditor.commit()(synchronous), soset/removereturn the true commit result. No manifest permission is required;SharedPreferencesis private storage. Like the UI pieces, the crate stages its own Java shim through[package.metadata.day.android]and rides the Day runtime (it needs the app’s JVM +Context). - Linux / Windows / HarmonyOS: a file-backed store, one flat
String -> Stringmap serialized under<config-dir>/day/<app-id>.store, where the app id isDay.toml’s[app].idasday build/day launchexport it (DAY_APP_ID; a binary built outside the CLI sharesday-part-prefs.store). The file is new per app: nothing is read from the shared file earlier builds wrote, by design (apps started over when the store became per-app, 2026-10).config-diris$XDG_CONFIG_HOME(else~/.config) on Linux,%APPDATA%(else%USERPROFILE%\AppData\Roaming) on Windows, and a best-effort app files dir on HarmonyOS. Each entry is a lineescaped_key=escaped_value; the escaper removes every raw=, newline, and carriage return (\→\\, newline →\n, CR →\r,=→\e), so the first raw=on a line is unambiguously the separator and a value may contain anything. Writes are best-effort atomic (write a sibling temp file, then rename over the target); a process-wide mutex serializes load-modify-save cycles. Every read tolerates a missing, unreadable, or corrupt file by treating the store as empty, so a partial write or a hand-edit can never panic a caller.setreturnsfalseonly when the store file could not be written (e.g. a read-only home). It depends onstdonly. - Web (web-dom):
localStorage, reached through the day-dom host shim’sday_dom_pref_*imports under aday.pref.key namespace. Values survive reloads and browser restarts, scoped per origin.localStoragecan throw (private browsing, storage pressure); failures report as uncommitted/absent, matching the contract everywhere else. The showcase’s Controls page binds its state through this store, so a reload keeps the counter (docs/web.md). On wasm outside a day-dom host page the imports are unresolved and instantiation fails. - Any other platform: a no-op store:
set/remove/containsreturnfalse,getreturnsNone.
What it shows about the extension system
Like day-part-battery and day-part-network, this is a headless external crate: it has no UI Piece
and registers nothing in any backend’s RENDERERS slice. On Android it stages its own Java shim
through [package.metadata.day.android] (with no permission this time, since private storage needs
none), which day build folds into the app’s Gradle build without touching any core day crate. On
every other platform it is fully day-independent (pure FFI on Apple, pure std file I/O on desktop).
See docs/extending.md.
Settings and About
pieces/day-piece-settings (docs/settings.md) is the typed, declarative layer
over this store: Pref<T> statics, groups and rows, the Settings and About surfaces, and the
env-wins rule for DAY_THEME/DAY_LOCALE. Its default Store is this crate. When testing
persistence by hand on macOS, unbundled binaries store under the process-name defaults domain
(defaults delete <name> clears it; a plist delete alone won’t, because cfprefsd caches).
Binding failures and verification
bind<T> requires FromStr + ToString + Clone + 'static. Invalid saved text leaves the initial
signal value unchanged. It persists later watched changes, not the initial default. Its callback
ignores set’s boolean result; use explicit writes when the UI must report a failure. Keep UI
messages in generated accessors and treat stored strings as data.
The example values above are synthetic fixtures, not UI translations. Compile-designated code
is in the storage recipe. Source evidence:
preferences API, including the memory feature and its
DAY_PREFS_DIR test override. A successful mock round trip does not certify platform durability.