Custom protocol activation

Registering custom scheme

iOS & macOS

Declare your custom URL scheme in info.plist in the platform head:

<key>CFBundleURLTypes</key>
<array>
    <dict>
    <key>CFBundleURLName</key>
    <string>My Useful Scheme</string>
    <key>CFBundleURLSchemes</key>
    <array>
        <string>my-scheme</string>
    </array>
    </dict>
</array>

Android

Register your protocol on the MainActivity with the [IntentFilter] attribute:

[IntentFilter(
    new [] {
        Android.Content.Intent.ActionView
    },
    Categories = new[] {
        Android.Content.Intent.CategoryDefault,
        Android.Content.Intent.CategoryBrowsable
    },
    DataScheme = "my-scheme")]

If your target framework is Android 12, you must also add Exported = true to the [Activity] attribute.

CategoryDefault is required (must be included for all implicit intents) and CategoryBrowsable is optional (allows opening the custom URI from the browser).

WASM

WASM implementation uses the Navigator.registerProtocolHandler API. This has several limitations for the custom scheme:

  • The custom scheme's name must begin with web+
  • The custom scheme's name must include at least 1 letter after the web+ prefix
  • The custom scheme must have only lowercase ASCII letters in its name.

To register the custom theme, call the WASM-specific Uno.Helpers.ProtocolActivation API when appropriate to let the user confirm URI handler association:

#if __WASM__
   Uno.Helpers.ProtocolActivation.RegisterCustomScheme(
      "web+myscheme", 
      new System.Uri("http://localhost:55838/"), 
      "Can we handle web+myscheme links?");
#endif

The first argument is the scheme name, the second is the base URL of your application (it must match the current domain to be registered successfully), and the third is a text prompt, which will be displayed to the user to ask for permission.

When a link with the custom scheme gets executed, the browser will navigate to a your URL with additional unoprotocolactivation query string key, which will contain the custom URI. Uno internally recognizes this query string key and executes OnActivated appropriately.

UWP

Works according to Windows docs. For more information, see Handle URI activation | Microsoft Docs.

Handling protocol activation

Custom URI activation can be handled by overriding the OnActivated method in App.cs or App.xaml.cs:

protected override void OnActivated(IActivatedEventArgs e)
{
    // Note: Ensure the root frame is created
    
    if (e.Kind == ActivationKind.Protocol)
    {
        var protocolActivatedEventArgs = (ProtocolActivatedEventArgs)e;
        var uri = protocolActivatedEventArgs.Uri;
        // do something
    }
}

Note that in line with UWP, if the application is not running, the OnLaunched method is not called and only OnActivated is executed instead. You must perform similar initialization of root app frame and activate the current Window at the end. If the application was running, this initialization can be skipped.

A full application lifecycle handling with shared logic between OnLaunched and OnActivated could look as follows:

protected override void OnLaunched(LaunchActivatedEventArgs e)
{
    var rootFrame = GetOrCreateRootFrame(e);
    if (e.PrelaunchActivated == false)
    {
        if (rootFrame.Content == null)
        {
            rootFrame.Navigate(typeof(MainPage), e.Arguments);
        }
        Window.Current.Activate();
    }
}

protected override void OnActivated(IActivatedEventArgs args)
{
    var rootFrame = GetOrCreateRootFrame(args);
    if (args.Kind == ActivationKind.Protocol)
    {
        var protocolActivatedEventArgs = (ProtocolActivatedEventArgs)args;
        var uri = protocolActivatedEventArgs.Uri;

        rootFrame.Navigate(typeof(DetailPage), uri.AbsoluteUri);
        Window.Current.Activate();
    }
}

private Frame GetOrCreateRootFrame(IActivatedEventArgs eventArgs)
{
    var rootFrame = Window.Current.Content as Frame;

    if (rootFrame == null)
    {
        rootFrame = new Frame();
        rootFrame.NavigationFailed += OnNavigationFailed;

        if (eventArgs.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // Load state from previously suspended application
        }

        Window.Current.Content = rootFrame;
    }

    return rootFrame;
}