URI start of MAUI Windows app creates a new instance. I need to have only one instance of app
Asked Answered
I

3

5

I am able to start my Windows MAUI app using an URI, and I can get the URI itself. But, it appears that a NEW instance of the app is being created. This is not ideal for me -- if my app is already running, I want to use that instance.

I have done something like this for a Xamarin.Forms app. I override OnActivated in Application class.

Re: my MAUI app, I'm not even clear on whether the issue is how I've done the "protocol" in package.appxmanifest, or if it is how I respond to lifecycle events.

Irrelevancy answered 21/10, 2022 at 23:17 Comment(0)
D
3

The default behaviour is to run multiple instances of your app. You can make the app single-instanced by defining a customized class with a Main method as suggested in this blog post:

[STAThread]
static async Task Main(string[] args)
{
    WinRT.ComWrappersSupport.InitializeComWrappers();
    bool isRedirect = await DecideRedirection();
    if (!isRedirect)
    {
        Microsoft.UI.Xaml.Application.Start((p) =>
        {
            var context = new DispatcherQueueSynchronizationContext(
                DispatcherQueue.GetForCurrentThread());
            SynchronizationContext.SetSynchronizationContext(context);
            new App();
        });
    }
    return 0;
}

private static async Task DecideRedirection()
{
    bool isRedirect = false;
    AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind;
    AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");

    if (keyInstance.IsCurrent)
    {
        keyInstance.Activated += OnActivated;
    }
    else
    {
        isRedirect = true;
        await keyInstance.RedirectActivationToAsync(args);
    }
    return isRedirect;
}

There is an open suggestion to simplify this process available on GitHub.

Dortch answered 23/10, 2022 at 18:16 Comment(3)
Thanks for the pointer. I ended up going to the article, but that did not get me far enough, so I went to the GitHub repo referenced. And, I added One point of confusion for MAUI, the "new App()" must reference the WINDOWS App class, not the generic. So I put the namespace in Program.cs code as <MyAppName>.WinUIIrrelevancy
Also, the mod to the add DISABLE_XAML_GENERATED_MAIN did not work for me -- I put this instead using the "property" editor for the project. Oh, and one last thing, this appears to require windows10.0.19041 or better (17736 did not work).Irrelevancy
@Irrelevancy I have the problem where I can't reference the windows app class, but only the one in root directoryHomologous
J
4

This solution basically, if you have any open instances, you cannot open new instance. Because WaitOne function is detect another instance and close new program before create new ones.(App.xaml.cs) (.NET MAUI)

 public partial class App : Application{

   private static Mutex mutex = new Mutex(true, Assembly.GetEntryAssembly().GetName().Name);

   public App()
   {
       if (!mutex.WaitOne(TimeSpan.Zero, true))
       {
           Current.Quit();
           Environment.Exit(0);
       }

       InitializeComponent();
       MainPage = new AppShell();

   }
}
Josuejosy answered 21/6, 2023 at 11:6 Comment(1)
This is only useful if you do not need any info of the URI. In case you need data from the URI activation, there is no way to get it.Newmark
D
3

The default behaviour is to run multiple instances of your app. You can make the app single-instanced by defining a customized class with a Main method as suggested in this blog post:

[STAThread]
static async Task Main(string[] args)
{
    WinRT.ComWrappersSupport.InitializeComWrappers();
    bool isRedirect = await DecideRedirection();
    if (!isRedirect)
    {
        Microsoft.UI.Xaml.Application.Start((p) =>
        {
            var context = new DispatcherQueueSynchronizationContext(
                DispatcherQueue.GetForCurrentThread());
            SynchronizationContext.SetSynchronizationContext(context);
            new App();
        });
    }
    return 0;
}

private static async Task DecideRedirection()
{
    bool isRedirect = false;
    AppActivationArguments args = AppInstance.GetCurrent().GetActivatedEventArgs();
    ExtendedActivationKind kind = args.Kind;
    AppInstance keyInstance = AppInstance.FindOrRegisterForKey("randomKey");

    if (keyInstance.IsCurrent)
    {
        keyInstance.Activated += OnActivated;
    }
    else
    {
        isRedirect = true;
        await keyInstance.RedirectActivationToAsync(args);
    }
    return isRedirect;
}

There is an open suggestion to simplify this process available on GitHub.

Dortch answered 23/10, 2022 at 18:16 Comment(3)
Thanks for the pointer. I ended up going to the article, but that did not get me far enough, so I went to the GitHub repo referenced. And, I added One point of confusion for MAUI, the "new App()" must reference the WINDOWS App class, not the generic. So I put the namespace in Program.cs code as <MyAppName>.WinUIIrrelevancy
Also, the mod to the add DISABLE_XAML_GENERATED_MAIN did not work for me -- I put this instead using the "property" editor for the project. Oh, and one last thing, this appears to require windows10.0.19041 or better (17736 did not work).Irrelevancy
@Irrelevancy I have the problem where I can't reference the windows app class, but only the one in root directoryHomologous
N
0

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:

  1. In OnLaunch, check if the Kind is Protocol. This would only happen to Instance2 (the instances that are launched by URI).
  2. 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.
  3. 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);
    }

}

Newmark answered 24/10, 2024 at 17:58 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.