This solution does not need a Program.cs
(I cannot add that file in a MAUI project) and you generally stay within MAUI code. It also works with WinUI3 and Windows App SDK. Note that this solution also only guarantees single instance in case of Protocol activation (i.e. no new window opened when launching an URI). You need additional or different logic if you want true single-instance.
I faced these issues:
I couldn't get any Activated or OnActivated event call with the Protocol.
A new window is opened instead.
The old instance (where the login window is waiting for example) does not receive any signals that a URI like my-app://login/result=xyz
has been called.
I found out Application lifecycle functionality migration, I recommend reading it, at least the section I linked, it will help you understand the issue and why I solved it this way. Note that despite linking to a Single Instance Apps, I only use it to solve the issue and the solution does not force your app to become a single-instance.
To summarize the article, the flow of Windows App SDK (which WinUI3 based on, which in turn MAUI based on) changed:
UWP |
Windows App SDK |
1. App Launch (Instance1) |
1. App Launch (Instance1) |
2. User opens my-app://url |
2. User opens my-app://url |
3. Instance1 receives OnActivated with Protocol |
3. App Launch (Instance2) with Protocol |
As you can see, in Windows App SDK, no Activated signal is given to your former Instance1
instance.
To fix that, we use RedirectActivationToAsync:
- In
OnLaunch
, check if the Kind
is Protocol
. This would only happen to Instance2
(the instances that are launched by URI).
- If it is, redirect those parameters to all other instances (in case you have multi-instance apps), or any custom logic you may want.
- For MAUI, we also do not call
base.OnLaunched
in this method so no new window is created.
- Instead, if it's not a launch by
Protocol
, register Activated
event to listen to Protocol event forwarded by Instance2
. NOTE for MAUI: you also have to do this for MAUI. For some reason the OnActivated
lifecycle is not called here.
Here's the solution, it's for MAUI but I believe it applies to WinUI3 and Windows App SDK as well:
Update: for MAUI, if you do not want to touch App.xaml.cs
, you can use the builder.ConfigureLifecycleEvents
:
// In your MauiProgram.cs or write it in a Windows platform-specific code:
builder.ConfigureLifecycleEvents(lc =>
{
lc.AddWindows(static win =>
{
win.OnLaunched(static async (app, args) =>
{
var appInstance = AppInstance.GetCurrent();
var e = appInstance.GetActivatedEventArgs();
if (e.Kind != ExtendedActivationKind.Protocol
|| e.Data is not ProtocolActivatedEventArgs protocol)
{
appInstance.Activated += AppInstance_Activated;
return;
}
var instances = AppInstance.GetInstances();
await Task.WhenAll(instances.Select(async q => await q.RedirectActivationToAsync(e)));
app.Exit();
});
});
});
private static void AppInstance_Activated(object? sender, AppActivationArguments e)
{
if (e.Kind != ExtendedActivationKind.Protocol ||
e.Data is not ProtocolActivatedEventArgs protocol)
{
return;
}
// Process your activation here
Debug.WriteLine("URI activated: " + protocol.Uri);
}
Old code that modify App.xaml.cs
:
// In your App.xaml.cs
public partial class App : MauiWinUIApplication
{
// ...
protected override async void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
var appInstance = AppInstance.GetCurrent();
var e = appInstance.GetActivatedEventArgs();
// If it's not a Protocol activation, just launch the app normally
if (e.Kind != ExtendedActivationKind.Protocol ||
e.Data is not ProtocolActivatedEventArgs protocol)
{
appInstance.Activated += AppInstance_Activated;
base.OnLaunched(args);
return;
}
// If it's a Protocol activation, redirect it to other instances
var instances = AppInstance.GetInstances();
await Task.WhenAll(instances
.Select(async q => await q.RedirectActivationToAsync(e)));
return;
}
private void AppInstance_Activated(object? sender, AppActivationArguments e)
{
if (e.Kind != ExtendedActivationKind.Protocol ||
e.Data is not ProtocolActivatedEventArgs protocol)
{
return;
}
// Process your activation here
Debug.WriteLine("URI activated: " + protocol.Uri);
}
}