Shiny Core
Shiny.Core is the foundational library for the Shiny ecosystem. It provides the hosting model, platform abstractions, lifecycle hooks, connectivity/battery monitoring, and the AOT-friendly type registry that all other Shiny modules build upon. Storage and DI registration are layered on via the Shiny.Extensions.DependencyInjection and Shiny.Extensions.Stores source-generated packages — these are pulled in transitively by Shiny.Core so you don't add them manually.
When to Use This Skill
- The user needs to set up Shiny hosting in a MAUI, native, Blazor, or Linux/macOS app
- The user asks about
IHost, HostBuilder, or UseShiny
- The user needs key-value storage (
IKeyValueStore, settings, secure store) via Shiny.Extensions.Stores
- The user wants source-generated persistence with
[Bind] partial properties or a service-attributed DI registration with [Service] / [Singleton] / [Scoped] / [Transient]
- The user asks about platform abstractions (
IPlatform, directories, main thread invocation)
- The user needs Android, iOS, or macOS lifecycle hooks (
IAndroidLifecycle, IIosLifecycle, IMacLifecycle)
- The user needs startup tasks (
IShinyStartupTask, ShinyLifecycleTask)
- The user asks about
AccessState, permission handling, or PermissionException
- The user needs network connectivity monitoring (
IConnectivity) or battery status (IBattery)
- The user needs an entity repository (
IRepository, IRepositoryEntity) — provided by Shiny.Extensions.Stores
- The user asks about remote configuration (
IRemoteConfigurationProvider) or Shiny.Extensions.Configuration
- The user needs observable collections (
INotifyReadOnlyCollection<T>, INotifyCollectionChanged<T>, BindingList<T>)
Library Overview
| Item |
Value |
| NuGet |
Shiny.Core (pulls in Shiny.Extensions.DependencyInjection + Shiny.Extensions.Stores) |
| Namespace |
Shiny, Shiny.Hosting, Shiny.Net, Shiny.Power, Shiny.Collections, Shiny.Extensions.Stores (storage), Shiny.Extensions.Configuration (remote config) |
| Platforms |
iOS, tvOS, Mac Catalyst, macOS, Android, Windows, Linux, Blazor WebAssembly, plain .NET |
tvOS
tvOS reuses the iOS platform layer wholesale — the same ShinyAppDelegate (from Shiny.Hosting.Native), IosPlatform, IosLifecycleExecutor and IIosLifecycle.* hooks. There is no tvOS-specific hosting code to write, and there is no MAUI on tvOS, so a tvOS head always hosts through Shiny.Hosting.Native — never generate UseShiny() / MauiProgram.cs guidance for tvOS.
Two Core APIs differ on tvOS:
IIosLifecycle.INotificationHandler does not exist on tvOS. A tvOS notification can only change the app icon badge, so there is no UNNotificationResponse and nothing is presented in the foreground. Code implementing it must be inside #if !TVOS.
IBattery reports BatteryState.Full / Level 1.0 permanently. An Apple TV is mains powered and UIDevice carries no battery API on tvOS. IBattery.Changed never fires there.
A complete UIKit tvOS host is in samples/Sample.tvOS — use its AppDelegate as the shape for tvOS hosting guidance.
Modules with a net10.0-tvos target: Shiny.Core, Shiny.Hosting.Native, Shiny.BluetoothLE, Shiny.Net.Discovery, Shiny.Jobs, Shiny.Net.Http, Shiny.Push, Shiny.ScreenRecorder, Shiny.Data.Sync. Modules with no tvOS target, because Apple withholds the underlying API: Shiny.BluetoothLE.Hosting (no peripheral role), Shiny.Net.Wifi (no NetworkExtension hotspot APIs), Shiny.Locations (no CLMonitor geofencing), Shiny.Notifications, Shiny.Contacts, Shiny.Calendar.
Companion Libraries
| NuGet |
Namespace |
Purpose |
Shiny.Hosting.Maui |
Shiny |
MAUI hosting integration (UseShiny) |
Shiny.Hosting.Native |
Shiny |
Native hosting base classes (ShinyAppDelegate, ShinyAndroidApplication, ShinyAndroidActivity) |
Shiny.Core.Linux |
Shiny |
Linux platform implementation + AddConnectivity() / AddBattery() |
Shiny.Core.Blazor |
Shiny |
Blazor WebAssembly platform implementation + AddConnectivity() / AddBattery() |
Shiny.Extensions.DependencyInjection |
Shiny |
Source-generated [Service] / [Singleton] / [Scoped] / [Transient] DI registration. Pulled in by Shiny.Core. |
Shiny.Extensions.Stores |
Shiny.Extensions.Stores |
IKeyValueStore, IRepository, source-generated [Bind] partial-property persistence, static Shiny.Stores.Default/Secure accessor. Pulled in by Shiny.Core. |
Shiny.Extensions.Stores.Web |
Shiny.Extensions.Stores |
Blazor WebAssembly localStorage / sessionStorage adapters (AddShinyWebAssemblyStores()) |
Shiny.Extensions.Serialization |
Shiny.Extensions.Serialization |
AOT-safe System.Text.Json serializer extensions used by Shiny modules |
Shiny.Extensions.Configuration |
Shiny.Extensions.Configuration |
Remote configuration provider and platform preferences |
Setup
MAUI Setup
In MauiProgram.cs, call UseShiny() on the MauiAppBuilder. This registers all core infrastructure services, the platform key/value stores, and lifecycle wiring automatically:
using Shiny;
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.UseShiny(); // Registers Shiny core services, stores, and lifecycle hooks
// Register your own services from [Service]/[Singleton]/[Scoped]/[Transient] attributes
builder.Services.AddGeneratedServices();
// Add device monitoring (these are no-ops if already registered)
builder.Services.AddConnectivity();
builder.Services.AddBattery();
return builder.Build();
}
}
Native (Non-MAUI) Setup
For native iOS apps, inherit from ShinyAppDelegate:
[Register("AppDelegate")]
public class AppDelegate : ShinyAppDelegate
{
protected override IHost CreateShinyHost()
{
var builder = HostBuilder.Create();
// Register services on builder.Services
return builder.Build();
}
}
For native Android apps, inherit from ShinyAndroidApplication and use ShinyAndroidActivity:
[Application]
public class MainApplication : ShinyAndroidApplication
{
public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) {}
protected override IHost CreateShinyHost()
{
var builder = HostBuilder.Create();
// Register services on builder.Services
return builder.Build();
}
}
[Activity(MainLauncher = true)]
public class MainActivity : ShinyAndroidActivity { }
Linux / macOS / plain .NET Setup
Shiny.Core.Linux provides the Linux IPlatform, IConnectivity, and IBattery implementations and is targeted at console / GTK apps. Use the same HostBuilder.Create() flow and call AddConnectivity() / AddBattery() from Shiny.Core.Linux if you need device monitoring.
Blazor WebAssembly Setup
For Blazor WASM, reference Shiny.Core.Blazor and call AddConnectivity() / AddBattery() to wire navigator-based monitoring. Both monitors load a JS module before they can report anything, so they self-start on the first property read or Changed subscription and report Unknown until that completes; await host.Services.UseShinyCore() after Build() starts them up front when the first read must be accurate. Only Chromium-based browsers expose the Network Information and Battery Status APIs — elsewhere ConnectionTypes and BatteryState stay Unknown (Access still works, it is navigator.onLine). Storage requires Shiny.Extensions.Stores.Web and a call to host.Services.UseShinyStores() after Build() so the static Shiny.Stores accessor snapshots the IJSRuntime-backed LocalStorageKeyValueStore.
Code Generation Instructions
When generating code that uses Shiny.Core, follow these conventions:
- Always call
UseShiny() in MAUI apps or inherit the proper native base classes. This is required before any Shiny module works.
- Register your own services via attributes from
Shiny.Extensions.DependencyInjection — [Service(ServiceLifetime.Singleton)], or the shortcuts [Singleton], [Scoped], [Transient]. The source generator emits services.AddGeneratedServices() — call it once during host build. Multiple interfaces, keyed services, and open generics are honoured automatically.
- Use the
[Bind] partial-property pattern from Shiny.Extensions.Stores for persisted settings instead of an INotifyPropertyChanged base class. The generator emits getters/setters that round-trip through the store with zero reflection — fully AOT/trim safe.
- Implement
IShinyStartupTask for code that should run immediately after the DI container is built. Register it with services.AddSingleton<IShinyStartupTask, MyTask>() or tag it [Singleton] and explicitly add the IShinyStartupTask interface.
- Inherit
ShinyLifecycleTask for startup tasks that also need foreground/background application-lifecycle events. It composes IAndroidLifecycle.IApplicationLifecycle, IIosLifecycle.IApplicationLifecycle, IMacLifecycle.IApplicationLifecycle, and IShinyStartupTask into one base class.
- Implement
IAndroidLifecycle.*, IIosLifecycle.*, or IMacLifecycle.* sub-interfaces for platform-specific lifecycle hooks. Register them in DI and the lifecycle executor dispatches to them automatically.
- Inject a keyed
IKeyValueStore from Shiny.Extensions.Stores via [FromKeyedServices(StoreKeys.Default)] IKeyValueStore store (or StoreKeys.Secure). The store factory is also available via IKeyValueStoreFactory.Get(alias).
- Use the static
Shiny.Stores.Default / Shiny.Stores.Secure accessors for one-off reads/writes outside DI contexts — the accessor self-bootstraps on first use after AddShinyStores() has run (call host.Services.UseShinyStores() after Build() on Blazor WASM).
- Use
IPlatform to access AppData, Cache, Public directories and InvokeOnMainThread().
- Use
AccessState enum and state.Assert() extension method to validate permissions before proceeding with platform operations.
- Use the
Changed C# events on IConnectivity and IBattery to react to network or battery state — these are no longer observable. Rx has been removed from Shiny.Core and Shiny.Jobs; only Shiny.BluetoothLE retains reactive streams. Subscribe with += handler and unsubscribe in your Dispose / page-leave hook.
- Use
IRepository for entity persistence, with entities implementing IRepositoryEntity (must have an Identifier property). The default implementation is a filesystem JSON store registered by services.AddDefaultRepository() and used internally by Locations, Notifications, and HTTP Transfers.
- For Blazor WASM, call
host.Services.UseShinyStores() immediately after builder.Build() so the static Shiny.Stores accessor captures the DI-resolved LocalStorageKeyValueStore (it needs IJSRuntime).
Conventions
- Shiny services are typically singletons; the
[Singleton] attribute is the right default.
- Persisted settings classes should be
partial class with [Bind] partial properties from Shiny.Extensions.Stores.
- Place startup tasks in a
Tasks/ or Infrastructure/ folder.
- Place settings classes in a
Settings/ or Models/ folder.
- Always handle
AccessState.Denied, AccessState.Disabled, and AccessState.NotSetup gracefully.
- Prefer C# events on Shiny.Core abstractions (
IConnectivity.Changed, IBattery.Changed) over Rx — Rx is intentionally absent from Core.
- Extension methods in
Shiny namespace are available when the appropriate package is referenced.
Namespace Ambiguities with MAUI
When using Shiny in a MAUI app, several Shiny types collide with MAUI implicit usings. Do NOT add all Shiny namespaces as global usings. Use explicit namespaces or FQNs for these:
| Type |
Shiny Namespace |
MAUI Namespace |
Resolution |
IConnectivity |
Shiny.Net |
Microsoft.Maui.Networking |
Use Shiny.Net.IConnectivity FQN |
IBattery |
Shiny.Power |
Microsoft.Maui.Devices |
Use Shiny.Power.IBattery FQN |
DeviceInfo |
Shiny.BluetoothLE |
Microsoft.Maui.Devices |
Use FQN for whichever you need |
Safe global usings (won't conflict with MAUI):
global using Shiny;
global using Shiny.Extensions.Stores; // IKeyValueStore, StoreKeys, [Bind]
global using Shiny.Jobs;
global using Shiny.Locations;
global using Shiny.BluetoothLE;
// Do NOT globally use: Shiny.Net, Shiny.Power, Shiny.Notifications, Shiny.Push, Shiny.BluetoothLE.Hosting
Best Practices
- Initialize Shiny early --
UseShiny() must be called in the builder chain before building the MAUI app. For native apps, the host must be created and Run() called in the application startup.
- Prefer attribute-based registration -- tag services with
[Singleton]/[Scoped]/[Transient] and let the Shiny.Extensions.DependencyInjection source generator emit AddGeneratedServices(). AOT-clean, no reflection at startup, multiple interfaces handled.
- Prefer
[Bind] partial properties for persisted settings instead of INotifyPropertyChanged plumbing. The generator emits getters/setters that round-trip through the configured IKeyValueStore.
- Keep startup tasks lightweight --
IShinyStartupTask.Start() runs synchronously on the main thread at startup.
- Use the right keyed store --
StoreKeys.Default for general preferences (backed by SharedPreferences / NSUserDefaults / ApplicationData.LocalSettings / localStorage), StoreKeys.Secure for sensitive data (Android Keystore / iOS Keychain / Windows secure storage).
- Use the static
Shiny.Stores.Default / Shiny.Stores.Secure / Shiny.Stores.Keyed(alias) accessor for one-off reads/writes outside DI.
- Third-party containers and keyed services -- Prism/DryIoc and other adapters that predate .NET 8 keyed services silently ignore
[FromKeyedServices] and resolve the plain service type instead. AddShinyStores() registers the default IKeyValueStore unkeyed as well, so Shiny's own platform types still build on those containers. In your code, use the static Shiny.Stores.Secure / Shiny.Stores.Keyed(...) accessor rather than [FromKeyedServices] for non-default stores when the app uses a non-Microsoft container — a container that drops the key will inject the wrong store (or fail with UnableToFindCtorWithAllResolvableArgs).
- Check
Host.IsInitialized before accessing Host.Current in code that may run before initialization.
- Use
BindingList<T> for thread-safe observable collections that can be bound to UI.
- Use the JSON contexts emitted by Shiny modules if you mix
Shiny.Extensions.Serialization with your own — Shiny.Jobs, Shiny.Locations, Shiny.Notifications, and Shiny.Net.Http each ship their own JsonSerializerContext for AOT safety.
Reference Files
- API Reference
- Public docs: https://shinylib.net/core/ (platform, lifecycle hooks, startup tasks, device monitoring, access & permissions, utilities, release notes)
1---2name: shiny-core3description: Core infrastructure, hosting, DI, key-value stores, lifecycle hooks, and platform abstractions for Shiny on .NET MAUI, iOS, Android, Mac Catalyst, macOS, Windows, Linux, and Blazor WebAssembly4---56# Shiny Core78Shiny.Core is the foundational library for the Shiny ecosystem. It provides the hosting model, platform abstractions, lifecycle hooks, connectivity/battery monitoring, and the AOT-friendly type registry that all other Shiny modules build upon. Storage and DI registration are layered on via the `Shiny.Extensions.DependencyInjection` and `Shiny.Extensions.Stores` source-generated packages — these are pulled in transitively by `Shiny.Core` so you don't add them manually.910## When to Use This Skill1112- The user needs to set up Shiny hosting in a MAUI, native, Blazor, or Linux/macOS app13- The user asks about `IHost`, `HostBuilder`, or `UseShiny`14- The user needs key-value storage (`IKeyValueStore`, settings, secure store) via `Shiny.Extensions.Stores`15- The user wants source-generated persistence with `[Bind]` partial properties or a service-attributed DI registration with `[Service]` / `[Singleton]` / `[Scoped]` / `[Transient]`16- The user asks about platform abstractions (`IPlatform`, directories, main thread invocation)17- The user needs Android, iOS, or macOS lifecycle hooks (`IAndroidLifecycle`, `IIosLifecycle`, `IMacLifecycle`)18- The user needs startup tasks (`IShinyStartupTask`, `ShinyLifecycleTask`)19- The user asks about `AccessState`, permission handling, or `PermissionException`20- The user needs network connectivity monitoring (`IConnectivity`) or battery status (`IBattery`)21- The user needs an entity repository (`IRepository`, `IRepositoryEntity`) — provided by `Shiny.Extensions.Stores`22- The user asks about remote configuration (`IRemoteConfigurationProvider`) or `Shiny.Extensions.Configuration`23- The user needs observable collections (`INotifyReadOnlyCollection<T>`, `INotifyCollectionChanged<T>`, `BindingList<T>`)2425## Library Overview2627| Item | Value |28|------------|---------------------------------|29| NuGet | `Shiny.Core` (pulls in `Shiny.Extensions.DependencyInjection` + `Shiny.Extensions.Stores`) |30| Namespace | `Shiny`, `Shiny.Hosting`, `Shiny.Net`, `Shiny.Power`, `Shiny.Collections`, `Shiny.Extensions.Stores` (storage), `Shiny.Extensions.Configuration` (remote config) |31| Platforms | iOS, tvOS, Mac Catalyst, macOS, Android, Windows, Linux, Blazor WebAssembly, plain .NET |3233### tvOS3435tvOS reuses the iOS platform layer wholesale — the same `ShinyAppDelegate` (from `Shiny.Hosting.Native`), `IosPlatform`, `IosLifecycleExecutor` and `IIosLifecycle.*` hooks. There is no tvOS-specific hosting code to write, and **there is no MAUI on tvOS**, so a tvOS head always hosts through `Shiny.Hosting.Native` — never generate `UseShiny()` / `MauiProgram.cs` guidance for tvOS.3637Two Core APIs differ on tvOS:3839- **`IIosLifecycle.INotificationHandler` does not exist on tvOS.** A tvOS notification can only change the app icon badge, so there is no `UNNotificationResponse` and nothing is presented in the foreground. Code implementing it must be inside `#if !TVOS`.40- **`IBattery` reports `BatteryState.Full` / `Level` 1.0 permanently.** An Apple TV is mains powered and `UIDevice` carries no battery API on tvOS. `IBattery.Changed` never fires there.4142A complete UIKit tvOS host is in `samples/Sample.tvOS` — use its `AppDelegate` as the shape for tvOS hosting guidance.4344Modules with a `net10.0-tvos` target: `Shiny.Core`, `Shiny.Hosting.Native`, `Shiny.BluetoothLE`, `Shiny.Net.Discovery`, `Shiny.Jobs`, `Shiny.Net.Http`, `Shiny.Push`, `Shiny.ScreenRecorder`, `Shiny.Data.Sync`. Modules with **no** tvOS target, because Apple withholds the underlying API: `Shiny.BluetoothLE.Hosting` (no peripheral role), `Shiny.Net.Wifi` (no NetworkExtension hotspot APIs), `Shiny.Locations` (no `CLMonitor` geofencing), `Shiny.Notifications`, `Shiny.Contacts`, `Shiny.Calendar`.4546### Companion Libraries4748| NuGet | Namespace | Purpose |49|-------|-----------|---------|50| `Shiny.Hosting.Maui` | `Shiny` | MAUI hosting integration (`UseShiny`) |51| `Shiny.Hosting.Native` | `Shiny` | Native hosting base classes (`ShinyAppDelegate`, `ShinyAndroidApplication`, `ShinyAndroidActivity`) |52| `Shiny.Core.Linux` | `Shiny` | Linux platform implementation + `AddConnectivity()` / `AddBattery()` |53| `Shiny.Core.Blazor` | `Shiny` | Blazor WebAssembly platform implementation + `AddConnectivity()` / `AddBattery()` |54| `Shiny.Extensions.DependencyInjection` | `Shiny` | Source-generated `[Service]` / `[Singleton]` / `[Scoped]` / `[Transient]` DI registration. Pulled in by `Shiny.Core`. |55| `Shiny.Extensions.Stores` | `Shiny.Extensions.Stores` | `IKeyValueStore`, `IRepository`, source-generated `[Bind]` partial-property persistence, static `Shiny.Stores.Default/Secure` accessor. Pulled in by `Shiny.Core`. |56| `Shiny.Extensions.Stores.Web` | `Shiny.Extensions.Stores` | Blazor WebAssembly `localStorage` / `sessionStorage` adapters (`AddShinyWebAssemblyStores()`) |57| `Shiny.Extensions.Serialization` | `Shiny.Extensions.Serialization` | AOT-safe System.Text.Json serializer extensions used by Shiny modules |58| `Shiny.Extensions.Configuration` | `Shiny.Extensions.Configuration` | Remote configuration provider and platform preferences |5960## Setup6162### MAUI Setup6364In `MauiProgram.cs`, call `UseShiny()` on the `MauiAppBuilder`. This registers all core infrastructure services, the platform key/value stores, and lifecycle wiring automatically:6566```csharp67using Shiny;6869public static class MauiProgram70{71 public static MauiApp CreateMauiApp()72 {73 var builder = MauiApp.CreateBuilder();74 builder75 .UseMauiApp<App>()76 .UseShiny(); // Registers Shiny core services, stores, and lifecycle hooks7778 // Register your own services from [Service]/[Singleton]/[Scoped]/[Transient] attributes79 builder.Services.AddGeneratedServices();8081 // Add device monitoring (these are no-ops if already registered)82 builder.Services.AddConnectivity();83 builder.Services.AddBattery();8485 return builder.Build();86 }87}88```8990### Native (Non-MAUI) Setup9192For native iOS apps, inherit from `ShinyAppDelegate`:9394```csharp95[Register("AppDelegate")]96public class AppDelegate : ShinyAppDelegate97{98 protected override IHost CreateShinyHost()99 {100 var builder = HostBuilder.Create();101 // Register services on builder.Services102 return builder.Build();103 }104}105```106107For native Android apps, inherit from `ShinyAndroidApplication` and use `ShinyAndroidActivity`:108109```csharp110[Application]111public class MainApplication : ShinyAndroidApplication112{113 public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) {}114115 protected override IHost CreateShinyHost()116 {117 var builder = HostBuilder.Create();118 // Register services on builder.Services119 return builder.Build();120 }121}122123[Activity(MainLauncher = true)]124public class MainActivity : ShinyAndroidActivity { }125```126127### Linux / macOS / plain .NET Setup128129`Shiny.Core.Linux` provides the Linux `IPlatform`, `IConnectivity`, and `IBattery` implementations and is targeted at console / GTK apps. Use the same `HostBuilder.Create()` flow and call `AddConnectivity()` / `AddBattery()` from `Shiny.Core.Linux` if you need device monitoring.130131### Blazor WebAssembly Setup132133For Blazor WASM, reference `Shiny.Core.Blazor` and call `AddConnectivity()` / `AddBattery()` to wire navigator-based monitoring. Both monitors load a JS module before they can report anything, so they self-start on the first property read or `Changed` subscription and report `Unknown` until that completes; `await host.Services.UseShinyCore()` after `Build()` starts them up front when the first read must be accurate. Only Chromium-based browsers expose the Network Information and Battery Status APIs — elsewhere `ConnectionTypes` and `BatteryState` stay `Unknown` (`Access` still works, it is `navigator.onLine`). Storage requires `Shiny.Extensions.Stores.Web` and a call to `host.Services.UseShinyStores()` after `Build()` so the static `Shiny.Stores` accessor snapshots the `IJSRuntime`-backed `LocalStorageKeyValueStore`.134135## Code Generation Instructions136137When generating code that uses Shiny.Core, follow these conventions:1381391. **Always call `UseShiny()`** in MAUI apps or inherit the proper native base classes. This is required before any Shiny module works.1402. **Register your own services via attributes from `Shiny.Extensions.DependencyInjection`** — `[Service(ServiceLifetime.Singleton)]`, or the shortcuts `[Singleton]`, `[Scoped]`, `[Transient]`. The source generator emits `services.AddGeneratedServices()` — call it once during host build. Multiple interfaces, keyed services, and open generics are honoured automatically.1413. **Use the `[Bind]` partial-property pattern from `Shiny.Extensions.Stores`** for persisted settings instead of an `INotifyPropertyChanged` base class. The generator emits getters/setters that round-trip through the store with zero reflection — fully AOT/trim safe.1424. **Implement `IShinyStartupTask`** for code that should run immediately after the DI container is built. Register it with `services.AddSingleton<IShinyStartupTask, MyTask>()` or tag it `[Singleton]` and explicitly add the `IShinyStartupTask` interface.1435. **Inherit `ShinyLifecycleTask`** for startup tasks that also need foreground/background application-lifecycle events. It composes `IAndroidLifecycle.IApplicationLifecycle`, `IIosLifecycle.IApplicationLifecycle`, `IMacLifecycle.IApplicationLifecycle`, and `IShinyStartupTask` into one base class.1446. **Implement `IAndroidLifecycle.*`, `IIosLifecycle.*`, or `IMacLifecycle.*` sub-interfaces** for platform-specific lifecycle hooks. Register them in DI and the lifecycle executor dispatches to them automatically.1457. **Inject a keyed `IKeyValueStore` from `Shiny.Extensions.Stores`** via `[FromKeyedServices(StoreKeys.Default)] IKeyValueStore store` (or `StoreKeys.Secure`). The store factory is also available via `IKeyValueStoreFactory.Get(alias)`.1468. **Use the static `Shiny.Stores.Default` / `Shiny.Stores.Secure` accessors** for one-off reads/writes outside DI contexts — the accessor self-bootstraps on first use after `AddShinyStores()` has run (call `host.Services.UseShinyStores()` after `Build()` on Blazor WASM).1479. **Use `IPlatform`** to access `AppData`, `Cache`, `Public` directories and `InvokeOnMainThread()`.14810. **Use `AccessState` enum** and `state.Assert()` extension method to validate permissions before proceeding with platform operations.14911. **Use the `Changed` C# events on `IConnectivity` and `IBattery`** to react to network or battery state — these are no longer observable. Rx has been removed from `Shiny.Core` and `Shiny.Jobs`; only `Shiny.BluetoothLE` retains reactive streams. Subscribe with `+= handler` and unsubscribe in your `Dispose` / page-leave hook.15012. **Use `IRepository`** for entity persistence, with entities implementing `IRepositoryEntity` (must have an `Identifier` property). The default implementation is a filesystem JSON store registered by `services.AddDefaultRepository()` and used internally by Locations, Notifications, and HTTP Transfers.15113. **For Blazor WASM**, call `host.Services.UseShinyStores()` immediately after `builder.Build()` so the static `Shiny.Stores` accessor captures the DI-resolved `LocalStorageKeyValueStore` (it needs `IJSRuntime`).152153### Conventions154155- Shiny services are typically singletons; the `[Singleton]` attribute is the right default.156- Persisted settings classes should be `partial class` with `[Bind]` partial properties from `Shiny.Extensions.Stores`.157- Place startup tasks in a `Tasks/` or `Infrastructure/` folder.158- Place settings classes in a `Settings/` or `Models/` folder.159- Always handle `AccessState.Denied`, `AccessState.Disabled`, and `AccessState.NotSetup` gracefully.160- Prefer C# events on Shiny.Core abstractions (`IConnectivity.Changed`, `IBattery.Changed`) over Rx — Rx is intentionally absent from Core.161- Extension methods in `Shiny` namespace are available when the appropriate package is referenced.162163## Namespace Ambiguities with MAUI164165When using Shiny in a MAUI app, several Shiny types collide with MAUI implicit usings. **Do NOT add all Shiny namespaces as global usings.** Use explicit namespaces or FQNs for these:166167| Type | Shiny Namespace | MAUI Namespace | Resolution |168|------|----------------|----------------|------------|169| `IConnectivity` | `Shiny.Net` | `Microsoft.Maui.Networking` | Use `Shiny.Net.IConnectivity` FQN |170| `IBattery` | `Shiny.Power` | `Microsoft.Maui.Devices` | Use `Shiny.Power.IBattery` FQN |171| `DeviceInfo` | `Shiny.BluetoothLE` | `Microsoft.Maui.Devices` | Use FQN for whichever you need |172173**Safe global usings** (won't conflict with MAUI):174```csharp175global using Shiny;176global using Shiny.Extensions.Stores; // IKeyValueStore, StoreKeys, [Bind]177global using Shiny.Jobs;178global using Shiny.Locations;179global using Shiny.BluetoothLE;180// Do NOT globally use: Shiny.Net, Shiny.Power, Shiny.Notifications, Shiny.Push, Shiny.BluetoothLE.Hosting181```182183## Best Practices184185- **Initialize Shiny early** -- `UseShiny()` must be called in the builder chain before building the MAUI app. For native apps, the host must be created and `Run()` called in the application startup.186- **Prefer attribute-based registration** -- tag services with `[Singleton]`/`[Scoped]`/`[Transient]` and let the `Shiny.Extensions.DependencyInjection` source generator emit `AddGeneratedServices()`. AOT-clean, no reflection at startup, multiple interfaces handled.187- **Prefer `[Bind]` partial properties** for persisted settings instead of `INotifyPropertyChanged` plumbing. The generator emits getters/setters that round-trip through the configured `IKeyValueStore`.188- **Keep startup tasks lightweight** -- `IShinyStartupTask.Start()` runs synchronously on the main thread at startup.189- **Use the right keyed store** -- `StoreKeys.Default` for general preferences (backed by SharedPreferences / NSUserDefaults / ApplicationData.LocalSettings / localStorage), `StoreKeys.Secure` for sensitive data (Android Keystore / iOS Keychain / Windows secure storage).190- **Use the static `Shiny.Stores.Default` / `Shiny.Stores.Secure` / `Shiny.Stores.Keyed(alias)` accessor** for one-off reads/writes outside DI.191- **Third-party containers and keyed services** -- Prism/DryIoc and other adapters that predate .NET 8 keyed services silently ignore `[FromKeyedServices]` and resolve the plain service type instead. `AddShinyStores()` registers the default `IKeyValueStore` unkeyed as well, so Shiny's own platform types still build on those containers. In *your* code, use the static `Shiny.Stores.Secure` / `Shiny.Stores.Keyed(...)` accessor rather than `[FromKeyedServices]` for non-default stores when the app uses a non-Microsoft container — a container that drops the key will inject the wrong store (or fail with `UnableToFindCtorWithAllResolvableArgs`).192- **Check `Host.IsInitialized`** before accessing `Host.Current` in code that may run before initialization.193- **Use `BindingList<T>`** for thread-safe observable collections that can be bound to UI.194- **Use the JSON contexts emitted by Shiny modules** if you mix `Shiny.Extensions.Serialization` with your own — Shiny.Jobs, Shiny.Locations, Shiny.Notifications, and Shiny.Net.Http each ship their own `JsonSerializerContext` for AOT safety.195196## Reference Files197198- [API Reference](reference/api-reference.md)199- Public docs: https://shinylib.net/core/ (platform, lifecycle hooks, startup tasks, device monitoring, access & permissions, utilities, release notes)