Some lessons I learned about MAUI

In this post, I want to share with you some lessons I learned about MAUI. In the last few months, I have been creating an application using NET8 MAUI. I faced a lot of issues and step-by-step I resolved most of them.

OnDisappearing

This method is called when the page disappears due to navigating away from the page within the app. It is not called when the app disappears due to an event external to the app (e.g., the user navigates to the home screen or another app, a phone call is received, the device is locked, and the device is turned off).

Like in the old Xamarin, I added this method

protected override void OnDisappearing()
{
        Shell.Current.GoToAsync("..", true);
        return base.OnBackButtonPressed();
}

With this code, the application goes back of 2 pages: one page from the system and another one because of the code. So, be careful what adding in this function.

Platform-specific XAML

Sometimes, I have to determine the behaviours of the application based on the platform. For example, if the application is running on Windows, I want to display a ContextMenu. For this reason, have to know the platform.

Here, we have a VerticalStackLayout and it has an x:Name set so that we can manipulate the contents of it by accessing it via the VerticalLayout identifier in the code-behind.

<Grid RowDefinitions="*,4*">

  <VerticalStackLayout
    Grid.Row="0"
    x:Name="VerticalLayout" />

</Grid>

Runtime decision

The simplest way to show platform-specific views is to make the appropriate calls in the code-behind based on the current runtime platform:

if (DeviceInfo.Platform == DevicePlatform.Android)
{
    VerticalLayout.Add(new Android.ViewAndroid());
}
else if (DeviceInfo.Platform == DevicePlatform.iOS)
{
    VerticalLayout.Add(new iOS.ViewiOS());
}
else if (DeviceInfo.Platform == DevicePlatform.macOS)
{
    VerticalLayout.Add(new iOS.ViewMacCatalyst());
}
else if (DeviceInfo.Platform == DevicePlatform.WinUI)
{
    VerticalLayout.Add(new iOS.ViewWindows());
}

The downside of this approach is that the resulting code will be available on all target platforms.

Conditional compilation

An alternative and slightly better approach compared to the runtime decision is to use conditional compilation instead:

#if ANDROID
        VerticalLayout.Add(new Android.ViewAndroid());
#elif IOS
    VerticalLayout.Add(new iOS.ViewiOS());
#elif MACCATALYST
    VerticalLayout.Add(new MacCatalyst.ViewMacCatalyst());
#elif WINDOWS
    VerticalLayout.Add(new Windows.ViewWindows());
#endif

The advantage of this is that only the appropriate calls end up in the compiled app code for each target platform and no decision needs to be taken during runtime.

XAML-only approach

It’s also possible to show only the relevant parts of the UI based on the current runtime platform by only using XAML and no C# code by taking advantage of <OnPlatform>:

<Grid RowDefinitions="*,4*">

  <ContentView
    Grid.Row="1"
    Padding="20">

	<OnPlatform x:TypeArguments="View">
		<On Platform="Android">
			<android:ImageViewAndroid />
		</On>
		<On Platform="iOS">
			<iOs:ImageViewiOS />
		</On>
		<On Platform="macOS">
			<mac:ImageViewUWP />
		</On>
		<On Platform="Windows">
			<windows:ImageViewUWP />
		</On>
	</OnPlatform>

  </ContentView>

</Grid>

Here, we have a ContentView that serves as a container and we control its content by using the <OnPlatform> class and providing different views for each platform. It’s important to include the x:TypeArguments="View" attribute, because we need to tell <OnPlatform> what the return type is as the ContentView class only accepts View instances as its Content.

Note: This is equivalent to the runtime decision approach in the code-behind, meaning that all views of all platforms will be included in the app bundle.

It’s also possible to use <OnIdiom> to provide different views depending on the device type (e.g. tablet, phone, desktop):

<Grid RowDefinitions="*,4*">

  <ContentView
    Grid.Row="1"
    Padding="20">

    <OnIdiom x:TypeArguments="View">
      <On Idiom="Tablet">
        <tablet:TabletView />
      </On>
      <On Idiom="Phone">
        <phone:PhoneView />
      </On>
    </OnIdiom>

  </ContentView>

</Grid>

MAUI and iOS: icon set or app icon set named “appicon” error

I would like to test it on an iOS simulator. When I try to run the application, I get this error

None of the input catalogs contained a matching stickers icon set or app icon set named “appicon”.

After binging a bit, I found this post on GitHub where they say:

In the info.plist you have

XSAppIconAssets Assets.xcassets/appicon.appiconset The AppIcon file name is icon.svg

What worked for me was making sure those name matched, ex rename the svg to appicon.svg or in info.plist to icon.appiconset

or

I had the same problem what fixed it for me was renaming my icon file from “orpheus_icon.png” to “orpheus.png” and changing this line in the info.plist to Assets.xcassets/orpheus.appiconset

Those answers were related to the NET7 and the previous version of MAUI. I think something has changed since then.

In my project, I have the folder Platform > iOS > AppIcon.appiconset and the autogenerated images. In Resources, I have the appicon.svg.

enter image description here

In the Info.plist, I have

<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>

Solution

Removing the bin and obj folders is not helping. The solution I found is to delete the Assets.appiconset from the iOS folder.

SecureStorage in Windows raises an error if saves an empty value

In my NET8 MAUI app, I’m using SecureStorage to save the user’s username. In Android, the function is working. I call the API to authenticate the user and as response, I get the UserId and UserName. Sometimes, the UserId is empty.

enter image description here

When I save the empty UserId with this code in Windows (working for Android)

await SecureStorage.Default.SetAsync("userId", login.UserId);
await SecureStorage.Default.SetAsync("username", login.UserName);

the app raises this error

System.ArgumentException: ‘Value does not fall within the expected range.’

enter image description here

Issue

This exception is coming from the implementation of the SecureStorage for Windows. It was caused by var buffer = await provider.ProtectAsync(bytes.AsBuffer()); . This should be a limit of the Windows native API. The null will also throw an exception about the value can’t be null.

So the string.Empty and null are not supported for the SecureStorage in the maui on the Windows platform.

Solution

I have to check if the value is not null and save only if it is not null. To remove a key use

SecureStorage.Default.Remove("userId");

and don’t try to add an empty string.

AppCenter crashes iOS application

In all my apps, I usually add AppCenter to track the events and the crashes. For this reason, I add the packages Microsoft.AppCenter.Analytics and Microsoft.AppCenter.Crashes packages to my projects. The issue is that when I try to deploy the application to an iOS Simulator I get this error:

clang++ exited with code 1: ld: in /Users/enrico/Library/Caches/Xamarin/mtbs/builds/LanguageInUse/1fa03704bb15e35c6f47a701d9d92131e3e0740198296a93338bb3c829bc9cf7/obj/Debug/net8.0-ios/iossimulator-arm64/linker-cache/AppCenterCrashes.a(MSACErrorReport.o), building for iOS Simulator, but linking in object file built for iOS, file ‘/Users/enrico/Library/Caches/Xamarin/mtbs/builds/LanguageInUse/1fa03704bb15e35c6f47a701d9d92131e3e0740198296a93338bb3c829bc9cf7/obj/Debug/net8.0-ios/iossimulator-arm64/linker-cache/AppCenterCrashes.a’ clang: error: linker command failed with exit code 1 (use -v to see invocation) LanguageInUse C:\Program Files\dotnet\packs\Microsoft.iOS.Sdk\17.2.8004\targets\Xamarin.Shared.Sdk.targets 1559

Also, I tried different Target iOS Framework but I get the same result.

Solution

The underlying issue seems to be that the AppCenter NuGet doesn’t support the ARM64 architecture in the simulator. As shared in this MAUI GitHub issue, you have to enter this in your CSPROJ file:

<ForceSimulatorX64ArchitectureInIDE>true</ForceSimulatorX64ArchitectureInIDE>

Orange selected ListView item

Here is an issue with MAUI that made me crazy for almost 2 months. I added a ListView on my page and displayed some data. When I tap on an item, the background color is orange.

Example of ListView - Some lessons I learned about MAUI
Example of ListView

This is a well-known issue in MAUI (see the issue on GitHub). To fix this issue, I had to create a custom ViewCell for each platform. Because the solution is quite long, I created a post for it. Jump on Orange selected ListView item highlighted in MAUI to see the full implementation.

Publish an Android app warnings for deobfuscation and native code

When I upload an app bundle or apk on the `Google Play console`, I get a warning

There is no deobfuscation file associated with this App Bundle. If you use obfuscated code (R8/proguard), uploading a deobfuscation file will make crashes and ANRs easier to analyse and debug. Using R8/proguard can help reduce app size.

This App Bundle contains native code, and you’ve not uploaded debug symbols. We recommend that you upload a symbol file to make your crashes and ANRs easier to analyse and debug.

Google Play console

So, the solution is to open the Properties of the project and then select the option for Android. There is a setting for R8 code shrinker. Check this option.

Android R8 code shrinker option - Some lessons I learned about MAUI
Android R8 code shrinker option

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.