Stores
Persist class properties across sessions with a single line of code. Shiny Stores provides a cross-platform key/value store abstraction that works on iOS, Android, Windows, and Blazor WebAssembly. Swap between Preferences, Secure Storage, Local Storage, and more — all behind the same API.
| GitHub | |
| Downloads |
Features
Section titled “Features”- Unified key/value store interface across all platforms
- Built-in stores for Preferences, Secure Storage, Local Storage, Session Storage, and In-Memory
- Source-generated property binding — mark partial properties with
[Bind]and the generator emits getter/setter bodies that round-trip through the store. NoINotifyPropertyChanged, no reflection - Static
Shiny.Storesaccessor for directStores.Default.Set/Get,Stores.Secure.Set/Get, and arbitrary keyed lookups - Custom store support — implement
IKeyValueStorefor your own storage backend
-
Install the NuGet package:
Terminal window dotnet add package Shiny.Extensions.Stores -
Register stores in your DI container — the static
Shiny.Storesaccessor self-bootstraps on first use:builder.Services.AddShinyStores();var host = builder.Build(); -
For Blazor WebAssembly, also install and register the web stores. Because
LocalStorageKeyValueStoreneedsIJSRuntimefrom the built provider, Blazor apps must callhost.Services.UseShinyStores()afterBuild()to snapshot the resolved store into the static accessor:Terminal window dotnet add package Shiny.Extensions.Stores.Webbuilder.Services.AddLocalStorageKeyValueStore();var host = builder.Build();host.Services.UseShinyStores(); // required for Blazor
Available Stores
Section titled “Available Stores”Each platform provides different store implementations, registered as keyed singletons in DI using StoreKeys:
| Platform | Key | Implementation |
|---|---|---|
| Android | StoreKeys.Default | SharedPreferences |
| Android | StoreKeys.Secure | EncryptedSharedPreferences |
| iOS / macOS | StoreKeys.Default | NSUserDefaults |
| iOS / macOS | StoreKeys.Secure | Keychain |
| Windows | StoreKeys.Default | ApplicationData.LocalSettings |
| Windows | StoreKeys.Secure | Secure Storage |
| Blazor WebAssembly | StoreKeys.Default | localStorage |
| Blazor WebAssembly | "session" | sessionStorage |
Using Stores Directly
Section titled “Using Stores Directly”The simplest path is the static Shiny.Stores accessor:
public class SettingsService{ public void SaveTheme(string theme) => Shiny.Stores.Default.Set("theme", theme); public string? GetTheme() => Shiny.Stores.Default.Get<string>("theme"); public void SaveToken(string token) => Shiny.Stores.Secure.Set("auth_token", token);}The accessor is self-bootstrapping on mobile/desktop — Default and Secure lazily construct the platform-native store on first access, so no post-build initialization is required. For Blazor (where IJSRuntime only exists post-build) call host.Services.UseShinyStores() after host.Build() to snapshot the DI-resolved store into the static accessor. For tests and custom keys, use Shiny.Stores.Register(key, store) to override or register an arbitrary IKeyValueStore, and Shiny.Stores.Reset() to clear between runs.
For DI-friendly access, inject the keyed IKeyValueStore directly:
public class SettingsService( [FromKeyedServices(StoreKeys.Default)] IKeyValueStore settings, [FromKeyedServices(StoreKeys.Secure)] IKeyValueStore secure){ public void SaveTheme(string theme) => settings.Set("theme", theme); public string GetTheme() => settings.Get<string>("theme") ?? "light";}Store Extension Methods
Section titled “Store Extension Methods”store.Get<T>(key, defaultValue); // Get with a default fallbackstore.GetRequired<T>(key); // Throws if key is not foundstore.SetOrRemove(key, value); // Removes the key if value is nullstore.SetDefault<T>(key, value); // Only sets if the key doesn't already existstore.IncrementValue(key); // Thread-safe integer incrementCustom Stores
Section titled “Custom Stores”Implement IKeyValueStore to create your own storage backend, then register it as a keyed singleton:
public class RedisKeyValueStore : IKeyValueStore{ public bool IsReadOnly => false; public T? Get<T>(string key) { /* ... */ } public void Set<T>(string key, T value) { /* ... */ } public bool Contains(string key) { /* ... */ } public bool Remove(string key) { /* ... */ } public void Clear() { /* ... */ }}
builder.Services.AddKeyedSingleton<IKeyValueStore, RedisKeyValueStore>("redis");
// DI lookup[FromKeyedServices("redis")] IKeyValueStore redis;
// To make a custom key visible to the static helper, register it explicitly.// (UseShinyStores only snapshots StoreKeys.Default / StoreKeys.Secure.)Shiny.Stores.Register("redis", host.Services.GetRequiredKeyedService<IKeyValueStore>("redis"));
Shiny.Stores.Keyed("redis").Set("key", "value");AI Coding Assistant
Section titled “AI Coding Assistant”Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skills Step 2 — Install the plugin:
claude plugin install shiny-extensions@shiny Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skills Step 2 — Install the plugin:
copilot plugin install shiny-extensions@shiny