| name | shiny-core |
| description | 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 WebAssembly |
| auto_invoke | true |
| triggers | ["shiny core","shiny setup","shiny host","HostBuilder","IHost","IPlatform","IKeyValueStore","IKeyValueStoreFactory","StoreKeys","AccessState","IShinyStartupTask","ShinyLifecycleTask","IAndroidLifecycle","IIosLifecycle","IMacLifecycle","UseShiny","AddShinyStores","AddGeneratedServices","Bind attribute","Service attribute","Singleton attribute","Scoped attribute","Transient attribute","NotifyPropertyChanged","key value store","settings store","secure store","platform abstraction","lifecycle hook","startup task","connectivity","IConnectivity","[Truncated]"] |
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();
builder.Services.AddGeneratedServices();
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();
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();
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. 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;
global using Shiny.Jobs;
global using Shiny.Locations;
global using Shiny.BluetoothLE;
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