Skip to content

Native Capabilities ​

Miko.Native defines cross-platform interfaces for device capabilities — camera, clipboard, filesystem, geolocation, notifications, and more. Your app depends only on the interfaces; each platform host supplies the implementation.

The interfaces follow the Ionic Capacitor v8 API surface, mapped to C#: Promise<T> becomes Task<T>, number becomes double, any becomes object?, Date becomes DateTimeOffset, and JavaScript listeners become C# events.

Setup ​

Register the interfaces once in your shared app assembly:

csharp
using Microsoft.Extensions.DependencyInjection;
using Miko.Native;

var builder = MikoAppBuilder.CreateDefault();
builder.Services.AddMikoNative();

Then have each platform head install its implementations:

csharp
// Desktop head (Miko.Windowing)
App.CreateContext(b => b.UseDesktopNative()).RunDesktop();

// Simulator head (Miko.Simulator)
App.CreateContext(b => b.UseSimulatorNative()).RunSimulator();

// Android Activity (Miko.Android)
MikoAndroidApp.CreateView(this, () => App.CreateContext(b => b.UseAndroidNative()));

// iOS AppDelegate (Miko.iOS)
new MikoViewController(App.CreateContext(b => b.UseIosNative()));

Order does not matter. AddMikoNative() registers defaults with TryAdd and the platform packages override with Replace, so the real implementation always wins whether you call it before or after.

Using a capability ​

Inject the interface into any component:

razor
@using Miko.Native.Camera

@code {
    [Inject] public ICameraService CameraService { get; set; } = default!;

    private async Task TakePhotoAsync()
    {
        var result = await CameraService.TakePhotoAsync(new TakePhotoOptions
        {
            Quality = 90,
            SaveToGallery = true,
        });

        _imagePath = result.WebPath;
    }
}

When a capability is unavailable ​

Injection always succeeds — every interface has a default registration. Calling a capability the current platform does not implement throws PlatformNotSupportedException rather than returning empty or fabricated data, so a missing capability cannot be mistaken for a real answer:

csharp
try
{
    var photo = await CameraService.TakePhotoAsync(new TakePhotoOptions());
}
catch (PlatformNotSupportedException)
{
    // No camera stack on this platform — fall back to a file picker.
}

Permission denials surface separately as UnauthorizedAccessException, and user cancellation as OperationCanceledException.

Capability support ​

InterfaceAndroidiOSDesktopSimulator
IAppService●●◐◐
IBrowserService●●◐◐
ICameraService●●—◑
IClipboardService●●◐◐
IDeviceService●●●◑
IFilesystemService●●●●
IGeolocationService●●—◑
IHapticsService●●—◑
IKeyboardService◐◐◐◐
ILocalNotificationService◐●—◑
IMotionService●●——
INetworkService●●●●
IPushNotificationService◐◐—◑
IScreenReaderService●●◐◑
ISplashScreenService———◑
IStatusBarService●●—◑
IToastService●●—◑

● full · ◐ partial (some members throw) · ◑ simulated · — not implemented

Two notes on the desktop column: IClipboardService and IScreenReaderService are Windows-only (Linux and macOS have no dependency-free system stack, so they throw), and capabilities marked partial are ones where the platform genuinely lacks the concept — for example ExitAppAsync() is Android-only, and the iOS-specific keyboard members target a WebView that Miko does not use.

Simulator behaviour ​

The simulator answers mobile-only capabilities with recognisable sample data and logs every call, so you can exercise the whole chain — tap button, call camera, render result — on the desktop instead of deferring it to a device. Sample URIs are prefixed miko-simulator:// and IDeviceService reports IsVirtual = true, so simulated values can never be mistaken for real device data. IDeviceService tracks the device you pick in the simulator panel, which makes it useful for checking platform-dependent UI.

Capabilities that already work on a desktop — filesystem, clipboard, network, browser — use the real desktop implementations rather than simulated ones.

Platform configuration ​

Permissions, manifests, and notification setup remain the app's responsibility; the interfaces expose only permission state and business parameters.

  • Android — declare permissions in AndroidManifest.xml (CAMERA, ACCESS_FINE_LOCATION, POST_NOTIFICATIONS, …). Apps using ICameraService must also forward OnActivityResult:

    csharp
    protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
    {
        if (MikoAndroidApp.HandleActivityResult(_view, requestCode, resultCode, data)) return;
        base.OnActivityResult(requestCode, resultCode, data);
    }

    Without this, camera and gallery calls wait forever — Android delivers the result to the Activity, not to the caller.

  • iOS — declare usage descriptions in Info.plist (NSCameraUsageDescription, NSLocationWhenInUseUsageDescription, NSPhotoLibraryUsageDescription, …). Calling a capability without its description crashes the app.

  • Push notifications — APNS certificates and FCM configuration, plus the corresponding AppDelegate / FirebaseMessagingService wiring, live in your app. Miko.Native does not bundle Firebase.

Unsubscribing from events ​

Capabilities that stream data hand back a watch id or expose C# events. Release them when the page goes away — a location watch left running keeps the GPS on:

csharp
private string? _watchId;

protected override async Task OnInitializedAsync()
{
    _watchId = await GeolocationService.WatchPositionAsync(
        new PositionOptions { EnableHighAccuracy = true },
        (position, error) => { /* … */ });
}

public async ValueTask DisposeAsync()
{
    if (_watchId is not null) await GeolocationService.ClearWatchAsync(_watchId);
}

Low-level native subscriptions implement INativeListener; call RemoveAsync() to detach. It is idempotent, so calling it from both an explicit teardown and Dispose is safe.

Released under the MIT License.