Skip to content

Enforce Snackbar usage only in packaged Windows apps - #3276

Open
ne0rrmatrix wants to merge 9 commits into
CommunityToolkit:mainfrom
ne0rrmatrix:FixComException
Open

Enforce Snackbar usage only in packaged Windows apps#3276
ne0rrmatrix wants to merge 9 commits into
CommunityToolkit:mainfrom
ne0rrmatrix:FixComException

Conversation

@ne0rrmatrix

@ne0rrmatrix ne0rrmatrix commented Aug 2, 2026

Copy link
Copy Markdown
Member

Description of Change

Fixes a regression introduced in CommunityToolkit.Maui v15.0.0 where calling SetShouldEnableSnackbarOnWindows(true) (or otherwise initializing the toolkit's Windows Snackbar/Toast support) in an unpackaged Windows app throws a System.Runtime.InteropServices.COMException (0x80670016):

Package dependency criteria could not be resolved.

or

Unable to load resource dll. Microsoft.WindowsAppRuntime.Insights.Resource.dll

Root Cause

Options.SetShouldEnableSnackbarOnWindows(true) unconditionally registered the app with the Windows App SDK's Microsoft.Windows.AppNotifications.AppNotificationManager, which internally resolves a Windows App Runtime package dependency via the dynamic dependency (Bootstrap) APIs. This registration/resolution only succeeds for packaged (MSIX) applications. When called from an unpackaged app, the underlying WinRT call (IAppNotificationManagerMethods.Register) fails with a COMException, since the required package dependency and its resource DLL (Microsoft.WindowsAppRuntime.Insights.Resource.dll) cannot be resolved outside of a packaged identity context.

Previously (v14.0.1) this registration path was not exercised for unpackaged apps, so the issue was not present until v15.0.0.

Fix

  • Options.SetShouldEnableSnackbarOnWindows now detects whether the app is running as a packaged app (IsPackagedApp(), via reflection over Windows.ApplicationModel.Package.Current) before calling AppNotificationManager.Default.Register()/Unregister(). For unpackaged apps, registration is skipped entirely and a Trace.WriteLine diagnostic message is emitted instead of allowing the unmanaged COMException to propagate.
  • Snackbar's constructor now throws a clear, actionable InvalidOperationException (instead of letting the unmanaged/WinRT COMException surface) when:
    • SetShouldEnableSnackbarOnWindows(true) was not called, or
    • the app is not packaged (MSIX) — since Snackbar on Windows depends on the Windows App SDK AppNotificationManager API, which requires a packaged app identity.
  • Both exceptions include a HelpLink pointing to the Snackbar documentation describing platform-specific initialization requirements.

Impact

  • Unpackaged Windows MAUI apps no longer crash with an opaque COMException/missing resource DLL error during startup or when calling SetShouldEnableSnackbarOnWindows(true).
  • Developers now get a clear, descriptive InvalidOperationException explaining that Snackbar requires a packaged (MSIX) Windows app, with a link to documentation.
  • Packaged Windows apps are unaffected; AppNotificationManager registration continues to work as before.

Additional Fix: Windows PRI Build Error (PRI175/PRI277)

While validating the above change, the Windows build of CommunityToolkit.Maui.Sample failed with:

WINAPPSDKGENERATEPROJECTPRIFILE : error PRI175: 0x80073b0f - Processing Resources failed with error: Duplicate Entry.
WINAPPSDKGENERATEPROJECTPRIFILE : error PRI277: 0x80073b0f - Conflicting values for resource 'Files/Microsoft.Maui/Platform/Windows/Styles/Resources.xbf'

Root Cause

CommunityToolkit.Maui.Sample.csproj referenced Microsoft.Maui.Controls with a wildcard version:

<PackageReference Include="Microsoft.Maui.Controls" Version="*" />

The wildcard resolved to 10.0.90, while every other MAUI package in the repo is pinned to 10.0.60 via $(MauiPackageVersion) in Directory.Build.props (including Microsoft.Maui.Controls.Maps and the MAUI assemblies pulled in transitively by the CommunityToolkit.Maui.* project references). The build output confirmed the conflict:

microsoft.maui.controls.core\10.0.90\...\Microsoft.Maui.Controls.dll
microsoft.maui.controls.maps\10.0.60\...\Microsoft.Maui.Controls.Maps.dll

Two different MAUI versions each ship the framework resource Files/Microsoft.Maui/Platform/Windows/Styles/Resources.xbf with different content. When the Windows App SDK PRI generator merges them, it finds the same resource path with conflicting values, producing PRI175/PRI277.

Fix

Pinned Microsoft.Maui.Controls to the repo-wide version so all MAUI packages align on 10.0.60:

<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiPackageVersion)" />

Note: The <WindowsSdkPackageVersion>10.0.19041.56</WindowsSdkPackageVersion> override in the sample project was initially suspected, but removing it surfaced a different error (MVVMTKCFG0003) — CommunityToolkit.Mvvm 8.4.2 requires that SDK version. That line is correct and was left in place; the actual culprit was the wildcard MAUI version.

Linked Issues

Fixes:

PR Checklist

Additional information

Unpackaged Sample app in Windows now builds and runs in Release mode.

Added runtime checks to ensure Snackbar is enabled and used only in packaged (MSIX) Windows apps, throwing clear exceptions and logging debug messages for unsupported scenarios. Introduced IsPackagedApp() helper method for packaging detection. Updated project file to use $(MauiPackageVersion) for Microsoft.Maui.Controls reference. Improved error handling and developer guidance.
Copilot AI review requested due to automatic review settings August 2, 2026 04:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a Windows regression where enabling/initializing Snackbar in unpackaged Windows apps can throw WinRT COMExceptions by gating Windows App SDK AppNotificationManager registration behind a “packaged app” check and by providing clearer, actionable exceptions when Snackbar is used without the required Windows setup. It also resolves a Windows PRI resource conflict in the sample by aligning the Microsoft.Maui.Controls package version with the repo-wide MAUI version.

Changes:

  • Guard Windows AppNotificationManager.Register/Unregister calls so they only run in packaged (MSIX) apps; emit a Trace.WriteLine diagnostic otherwise.
  • Add a Windows-only Snackbar constructor check that throws a clear InvalidOperationException for unpackaged apps (with HelpLink).
  • Pin Microsoft.Maui.Controls in the sample project to $(MauiPackageVersion) to avoid mixed-MAUI resource conflicts.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/CommunityToolkit.Maui/Options.shared.cs Gates Windows App SDK notification registration/unregistration behind packaged-app detection and logs diagnostics when unsupported.
src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.shared.cs Adds a Windows-only guard to throw a clearer exception when Snackbar is used in an unpackaged app.
samples/CommunityToolkit.Maui.Sample/CommunityToolkit.Maui.Sample.csproj Aligns Microsoft.Maui.Controls package version with the repo-wide MAUI version to prevent PRI build conflicts.

Comment thread src/CommunityToolkit.Maui/Options.shared.cs
Comment thread src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.shared.cs
Comment thread src/CommunityToolkit.Maui/Alerts/Snackbar/Snackbar.shared.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Alerts.Snackbar.HandleSnackbarAction(args);
}

static bool IsPackagedApp()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to refactor this so that it doesn't require a try/catch block? Using a try/catch block is an expensive operation and if we can avoid it, we should. But if we can't, that's ok too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed try/catch and replaced it with a reflection call

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to not use reflection now and it returns true for non null

internal static TimeSpan GetDefaultTimeSpan() => TimeSpan.FromSeconds(3);

#if WINDOWS
static bool IsPackagedApp()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to refactor this method so that it doesn't require a try/catch block? Using a try/catch block is an expensive operation and if we can avoid it, we should. But if we can't, that's ok too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed try/catch and replaced it with a reflection call

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to not use reflection now and it returns true for non null

<MauiFont Include="Resources\Fonts\*" />

<PackageReference Include="Microsoft.Maui.Controls" Version="*" />
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiPackageVersion)" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

We keep Microsoft.Maui.Controls version as * to ensure the sample app always uses the most recent MuGet release. This allows us to run + test the sample app on the latest version to avoid breaking changes in our library which runs on an older version of Microsoft.Maui.Controls.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wildcard * caused the sample app to resolve Microsoft.Maui.Controls to the latest NuGet version (10.0.90), while every other MAUI package in the repo — including Microsoft.Maui.Controls.Maps, Microsoft.Maui.Core, and the MAUI assemblies pulled in transitively by the CommunityToolkit.Maui.* project references — was pinned to 10.0.60 via $(MauiPackageVersion).

This version mismatch broke the Windows build because both MAUI 10.0.60 and 10.0.90 ship the same framework resource file (Files/Microsoft.Maui/Platform/Windows/Styles/Resources.xbf) but with different content. When the Windows App SDK PRI generator merged resources from both versions, it found the same path with conflicting values, producing:

By pinning the sample to $(MauiPackageVersion) (10.0.60), all MAUI packages resolve to the same version, eliminating the duplicate resource conflict.

Replaced try-catch with reflection in IsPackagedApp() for Windows in Snackbar.shared.cs and Options.shared.cs. The method now checks for Windows.ApplicationModel.Package via reflection and inspects the static Current property, improving robustness and avoiding direct API calls.
@TheCodeTraveler

Copy link
Copy Markdown
Collaborator

Thanks James! Could you open up a Docs PR to note this new Snackbar/Toast behavior for unpackaged apps?

@TheCodeTraveler TheCodeTraveler added the pending documentation This feature requires documentation label Aug 2, 2026
@ne0rrmatrix

Copy link
Copy Markdown
Member Author

Thanks James! Could you open up a Docs PR to note this new Snackbar/Toast behavior for unpackaged apps?

Documentation created! MicrosoftDocs/CommunityToolkit#655

};
}

if (!IsPackagedApp())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably should be the first check before configurations

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated as suggested

@ne0rmatrix

Copy link
Copy Markdown

I reverted to using try catch again. Otherwise we do not control what the throw message is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending documentation This feature requires documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants