Device Monitoring
Shiny.Core includes two small monitors: IConnectivity for network reachability and IBattery for power state. Shiny’s modules use them to decide when to run. Jobs, for example, use them to honour RequiredInternetAccess and BatteryNotLow. You can inject them in your own code too.
Registration
Section titled “Registration”AddShinyCoreServices doesn’t register either monitor, but the modules that need them add them for you:
| Module call | Adds |
|---|---|
AddJob (Android, iOS, tvOS, Mac Catalyst, Windows) |
IConnectivity, IBattery |
AddHttpTransfers / AddHttpClientTransfers / AddDataSync (platform targets) |
IConnectivity |
AddBlazorHttpTransfers / AddBlazorDataSync |
IConnectivity, IBattery |
To use them yourself, or on plain .NET (Linux, console) where the modules can’t add them, register them directly:
services.AddConnectivity();services.AddBattery();Neither replaces an existing registration, so they are safe to call even if a module already registered them. The call is the same everywhere, and it all comes from Shiny.Core: on the plain net10.0 build it picks the browser implementation on Blazor WebAssembly (OperatingSystem.IsBrowser()) and the Linux one on Linux (OperatingSystem.IsLinux()). Plain .NET on Windows or macOS gets the System.Net.NetworkInformation connectivity monitor and no battery, because there is no battery API to read there.
Using your own implementation
Section titled “Using your own implementation”Every one of these registrations is a TryAdd, so an implementation you register first wins and the module leaves it alone:
services.AddSingleton<Shiny.Net.IConnectivity, MyConnectivity>();services.AddSingleton<Shiny.Power.IBattery, MyBattery>();services.AddJob<MyJob>(); // keeps MyConnectivity / MyBatteryRegister it before the module call. Registered afterwards, both implementations end up in the container.
Connectivity
Section titled “Connectivity”public interface IConnectivity{ event EventHandler? Changed; ConnectionTypes ConnectionTypes { get; } // [Flags] None, Unknown, Bluetooth, Wired, Wifi, Cellular NetworkAccess Access { get; } // Unknown, None, Local, ConstrainedInternet, Internet}public class SyncService(Shiny.Net.IConnectivity connectivity) : IDisposable{ public void Start() => connectivity.Changed += this.OnChanged;
void OnChanged(object? sender, EventArgs e) { if (connectivity.IsInternetAvailable()) _ = this.FlushOutbox(); }
public void Dispose() => connectivity.Changed -= this.OnChanged;}IsInternetAvailable(allowConstrained: true) returns true for Internet, and also for ConstrainedInternet (a captive portal or low-data mode) unless you pass false. Check ConnectionTypes.HasFlag(ConnectionTypes.Wifi) if you want to hold large transfers until the device is on Wi-Fi.
Battery
Section titled “Battery”public interface IBattery{ event EventHandler? Changed; BatteryState Status { get; } // Unknown, None, Charging, Full, NotCharging, Discharging double Level { get; } // 0.0 – 1.0 BatteryPowerSource PowerSource { get; } // Unknown, Battery, AC, Usb, Wireless EnergySaverStatus EnergySaverStatus { get; } // Unknown, On, Off}IsPluggedIn() returns true for Charging, Full and None. None means no battery was found, which means the device is on mains power.
PowerSource says where power comes from. Only Android and Linux can tell USB and wireless charging from mains; elsewhere any external power reads AC. EnergySaverStatus is the operating system’s energy saver: Low Power Mode on iOS, Mac Catalyst and macOS 12+, Battery Saver on Android, Energy Saver on Windows, and the power-saver profile on Linux. Changed fires when either changes, as well as the status and level.
Events
Section titled “Events”Changed has no payload. Read Access / ConnectionTypes or Status / Level inside the handler. On most platforms the native listener only starts when the first handler subscribes and stops when the last one unsubscribes, so remember to unsubscribe, for example in Dispose or when a page is left. The properties always return a fresh reading, whether or not anything is subscribed.
Platform Backends
Section titled “Platform Backends”| Platform | Connectivity | Battery |
|---|---|---|
| Android | ConnectivityManager.NetworkCallback |
ACTION_BATTERY_CHANGED broadcast (with EXTRA_PLUGGED for the power source); PowerManager.IsPowerSaveMode and ACTION_POWER_SAVE_MODE_CHANGED for energy saver |
| iOS / Mac Catalyst | NWPathMonitor |
UIDevice battery notifications; NSProcessInfo Low Power Mode and its power-state notification |
| tvOS | NWPathMonitor |
None. An Apple TV is mains powered and UIDevice has no battery API there, so it always reports Full / 1.0 / AC / energy saver Off, and Changed never fires |
| macOS (AppKit) | NWPathMonitor |
IOKit power sources (IOPSNotificationCreateRunLoopSource); NSProcessInfo Low Power Mode on macOS 12+, Unknown before |
| Windows | NetworkInformation.NetworkStatusChanged |
Battery.AggregateBattery; PowerManager.PowerSupplyStatus and PowerManager.EnergySaverStatus |
| Linux | System.Net.NetworkInformation (netlink change events) |
sysfs /sys/class/power_supply, polled every 5 seconds while subscribed. The first online Mains / USB / Wireless supply is the power source. Energy saver is On when /sys/firmware/acpi/platform_profile is low-power (power-profiles-daemon’s power-saver), Unknown without one. Reports None / 1.0 / AC on machines without a battery |
| Blazor WASM | navigator.onLine + Network Information API |
Battery Status API (navigator.getBattery). Charging reads as AC; energy saver is always Unknown |
On Linux, Access reports Internet whenever a non-loopback interface is up. It does not check whether the internet is actually reachable.
Blazor WebAssembly
Section titled “Blazor WebAssembly”The browser implementations load a JavaScript module before they can report anything, and a module import cannot be awaited from a synchronous property. So both monitors start themselves the first time you read a property or subscribe to Changed, and report Unknown (with a Level of 1.0) until that finishes. A Changed event follows once the browser reports one, so a page that subscribes in OnInitialized updates by itself.
UseShiny() starts the Shiny host - running its startup tasks, such as the in-process job runner - and then awaits both monitors, so the very first read is accurate. Call it after building the host:
var host = builder.Build();await host.Services.UseShiny(); // runs startup tasks, then starts whichever monitors were registeredawait host.RunAsync();Browser support varies, and the monitors say so rather than guessing. Access uses navigator.onLine, which works everywhere. Connection type and battery come from the Network Information and Battery Status APIs, which only Chromium-based browsers implement. On Firefox and Safari, ConnectionTypes stays Unknown and Status stays Unknown with a Level of 1.0.


