Skip to content

Stateful Services

Stateful services are a simple way to maintain service state across app restarts. You write a regular singleton service, declare the properties you want to persist, and Shiny automatically reads them back from storage on startup and writes them back whenever they change — no manual save/load code.

Under the hood this is powered by Shiny’s object store binder, which wires an INotifyPropertyChanged object to an IKeyValueStore.

  1. The service must be registered as a singleton (use AddShinyService<T>).
  2. The service must implement System.ComponentModel.INotifyPropertyChanged. The easiest way is to inherit from Shiny.NotifyPropertyChanged.
  3. Every property you want to persist must have a public get and set.
  4. Property types must be “simple” (primitives, string, DateTime, Guid, enums) or JSON-serializable.
using Shiny;
using Shiny.Stores;
namespace MyApp.Services;
// Optional: pick which store backs the service.
// Defaults to "settings". Use "secure" for sensitive data.
[ObjectStoreBinder("settings")]
public class MyStatefulService : NotifyPropertyChanged
{
string userName = string.Empty;
public string UserName
{
get => this.userName;
set => this.Set(ref this.userName, value);
}
bool isDarkMode;
public bool IsDarkMode
{
get => this.isDarkMode;
set => this.Set(ref this.isDarkMode, value);
}
DateTime? lastSyncedAt;
public DateTime? LastSyncedAt
{
get => this.lastSyncedAt;
set => this.Set(ref this.lastSyncedAt, value);
}
}
using Shiny;
// wherever you register your services with the service collection
builder.Services.AddShinyService<MyStatefulService>();

AddShinyService<T>() is what triggers the binding. A plain AddSingleton<T>() registration will not persist — this is the most common reason properties “silently don’t save”.

Inject your service like any other singleton. Reads come back with whatever was persisted last; writes are flushed to the store immediately.

public class SettingsViewModel
{
readonly MyStatefulService state;
public SettingsViewModel(MyStatefulService state)
{
this.state = state;
}
public void ToggleDarkMode()
{
// This assignment is automatically persisted
this.state.IsDarkMode = !this.state.IsDarkMode;
}
}
Store AliasBacked ByUse For
"settings" (default)NSUserDefaults (iOS) / SharedPreferences (Android)General preferences and non-sensitive state
"secure"iOS Keychain / Android KeystoreAuth tokens, API keys, anything sensitive
"memory"In-memory onlyTesting, transient state that should not persist

Apply the desired alias via [ObjectStoreBinder("secure")] on the class, or pass the store alias to the IObjectStoreBinder.Bind call if you are binding manually.