András Tóth‘s professional blog
banditoth.net

Hey there 👋, I’m banditoth a .NET MAUI developer from Hungary.
I write about software development with .NET technologies.

You can find me on:
LinkedIn | Github | StackOverflow | X / Twitter | Threads

Tag: .NET MAUI

  • Debug .NET MAUI Android apps with Android work profile

    An Android Work Profile is a feature that allows you to separate work apps and data from personal apps and data on the same device. This is particularly useful for maintaining privacy and security in a corporate environment. However when you are developing an app for your company, who’s got these configurations on their mobile devices, you might find yourself in a tricky solution, because VS Code simply installs the app on the workprofile and on the normal profile aswell, but only can run with debug on the normal profile without any configuration.

    Get the users of the Android device

    To list users on an Android device using ADB (Android Debug Bridge), you can use the following command:

    adb shell pm list users
    

    This command will display a list of users on the device, including their user IDs.

    For example, the output might look something like this:

    Users:
        UserInfo{0:Owner:13} running
        UserInfo{10:Work:30} running
    

    Configure the .csproj to launch the app on work profile

    Insert the following line within the <PropertyGroup> section of your .csproj file:

    <AndroidDeviceUserId>10</AndroidDeviceUserId>
    

    This attribute specifies the user ID for the Android Work Profile. The user ID 10 is commonly used for work profiles, but you should verify this for your specific setup.

    Last but not least: Hit F5 and Run your project 🙂

    Remark: This solution is only working in Visual Studio for Windows, and Visual Studio Code on mac.

    1. .NET MAUI Hide software keyboard when tapping out of an Entry on iOS

      To hide the software keyboard when the user taps outside the entry field, set the HideSoftInputOnTapped property to True in your ContentPage definition.

      &lt;ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                   x:Class="MyMauiApp.MainPage"
                   HideSoftInputOnTapped="True">
      
          &lt;StackLayout Padding="10">
              &lt;Entry Placeholder="Tap here to enter text" />
          &lt;/StackLayout>
      &lt;/ContentPage>
      
      

      Please note that the HideSoftInputOnTapped property might not work as expected when tapping on certain controls like ScrollView. In such cases, you might need to implement a custom behavior to handle keyboard dismissal.

    2. Customizing .NET MAUI Map’s pin Z-Order, Rotation, and Anchor point – Android

      This post will build upon the excellent tutorial on customizing map pins in .NET MAUI by Vladislav Antonyuk, and focus on adding these specific adjustments for Android.

      • Z-order: This determines the drawing order of the pins. Pins with higher Z-order values appear on top of those with lower values.
      • Rotation: Controls the rotation angle of the pin in degrees.
      • Anchor: Defines the point on the pin image that corresponds to the pin’s location on the map. The anchor is defined in terms of a normalized coordinate system where (0, 0) is the top-left corner and (1, 1) is the bottom-right corner of the image.

      Define the Anchor Points enum, get a method to return anchor floats

      We start by defining an enum to represent the different anchor points. A helper method, GetAnchorPoint, will return the appropriate anchor coordinates based on the chosen enum value.

      public enum AnchorPointType
      {
          TopLeft,
          TopCenter,
          TopRight,
          Center,
          BottomLeft,
          BottomCenter,
          BottomRight,
          LeftCenter,
          RightCenter
      }
      

      This method returns the normalized coordinates for each anchor point, which can then be applied to the pin’s icon.

      private static (float, float) GetAnchorPoint(AnchorPointType anchor)
      {
          return anchor switch
          {
              AnchorPointType.TopLeft => (0.0f, 0.0f),
              AnchorPointType.TopCenter => (0.5f, 0.0f),
              AnchorPointType.TopRight => (1.0f, 0.0f),
              AnchorPointType.Center => (0.5f, 0.5f),
              AnchorPointType.BottomLeft => (0.0f, 1.0f),
              AnchorPointType.BottomCenter => (0.5f, 1.0f),
              AnchorPointType.BottomRight => (1.0f, 1.0f),
              AnchorPointType.LeftCenter => (0.0f, 0.5f),
              AnchorPointType.RightCenter => (1.0f, 0.5f),
              _ => (0.5f, 0.5f)  // Default to center if undefined
          };
      }
      

      Modifications to the Custom Map Handler

      Now, let’s focus on the custom map handler, where you’ll need to modify how each pin is added to the map. Following the setup from Vladislav’s tutorial, you only need to adjust the following parts in the OnMapReady method for Android:

      cp.ImageSource.LoadImage(MauiContext, result =>
      {
          if (result?.Value is BitmapDrawable bitmapDrawable)
          {
              var originalBitmap = bitmapDrawable.Bitmap;
      
              // Set the custom icon for the pin
              markerOption.SetIcon(BitmapDescriptorFactory.FromBitmap(originalBitmap));
      
              // Set the rotation of the pin
              markerOption.SetRotation((float)cp.Rotation);
      
              // Set the anchor of the pin based on the chosen AnchorPointType
              var anchor = GetAnchorPoint(cp.Anchor);
              markerOption.Anchor(anchor.Item1, anchor.Item2);
      
              // Set the Z-order to bring this pin to the top
              markerOption.InvokeZIndex(cp.ZOrder);
          }
      
          AddMarker(Map, pin, Markers, markerOption);
      });
      
      
    3. Disabling Map Zoom Controls and Other Buttons in .NET MAUI Android

      In this post, we’ll guide you through the process of disabling these UI elements (zoom controls, compass, and location buttons) in your Android map implementation using a custom map handler. This approach gives you more control over the user experience and map functionality in your mobile app.

      Creating a Custom Map Handler

      To disable these controls, we need to customize how the map is rendered on Android. This involves creating a custom map handler that intercepts the way the map is displayed and adjusts its settings.

      A detailed tutorial for creating custom map handlers can be found in this great guide by Vladislav Antonyuk. We will extend that concept here.

      First, we need to implement a MapCallbackHandler that disables specific controls when the map is ready. This is done in the OnMapReady method, which is triggered when the map is fully loaded and ready for interaction.

      using Android.Gms.Maps;
      using Android.Gms.Maps.Model;
      using Microsoft.Maui.Handlers;
      using Microsoft.Maui.Controls.Compatibility.Maps.Android;
      
      class MapCallbackHandler : Java.Lang.Object, IOnMapReadyCallback
      {
          private readonly IMapHandler mapHandler;
      
          public MapCallbackHandler(IMapHandler mapHandler)
          {
              this.mapHandler = mapHandler;
          }
      
          public void OnMapReady(GoogleMap googleMap)
          {
              // Update map with any pins or map state changes
              mapHandler.UpdateValue(nameof(IMap.Pins));
              
              // Disable zoom controls
              googleMap.UiSettings.ZoomControlsEnabled = false;
              
              // Disable the "My Location" button
              googleMap.UiSettings.MyLocationButtonEnabled = false;
              
              // Disable the compass
              googleMap.UiSettings.CompassEnabled = false;
      
              // Additional settings can be adjusted here, such as disabling tilt or gestures.
          }
      }
      
      

      In the OnMapReady method, we access the googleMap.UiSettings property, which contains several settings that control the map’s UI. In our example, we set the following to false:

      • ZoomControlsEnabled: Disables the zoom buttons.
      • MyLocationButtonEnabled: Removes the My Location button that appears when location services are enabled.
      • CompassEnabled: Hides the compass that appears when the user rotates the map.

      You can also adjust other settings here, such as disabling tilt gestures or zoom gestures if needed.

    4. Using Different Entitlements for Debug and Release Modes in .NET MAUI – iOS

      When developing a mobile app using .NET MAUI, particularly for iOS, it’s essential to configure your application differently for debug and release modes. One of these differences is the APS-environment setting, which dictates how your app communicates with Apple Push Notification services (APNs) during development and production.

      What is Entitlements.plist?

      The Entitlements.plist is a property list (plist) file that defines various capabilities or entitlements for your app. Entitlements are special permissions that allow your app to use certain services provided by iOS, such as iCloud, In-App Purchases, or push notifications.

      For push notifications, the Entitlements.plist file contains the APS-environment key, which indicates to Apple whether your app is in development or production mode. Based on this, the app uses either the sandbox or production APNs.

      What is APS-environment?

      The APS-environment (Apple Push Services environment) is an entitlement used to specify the environment for push notifications. This entitlement informs Apple’s servers whether the app is running in a development environment or in production, determining which server to send the notifications through:

      • Development APS-environment: Used for testing push notifications during the app’s development phase. These notifications go through Apple’s sandbox APNs server.
      • Production APS-environment: Used for apps that have been published and distributed through the App Store. Notifications go through Apple’s production APNs server.

      This configuration helps separate testing from live user interactions and avoids accidental notification delivery to users during testing.

      Configuring Different APS-environments for Debug and Release

      To configure different environments for Debug and Release modes in your .NET MAUI project, you can modify your .csproj file as follows:

      &lt;PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|net8.0-ios|AnyCPU'">
        &lt;CodesignEntitlements>Platforms\iOS\Entitlements.plist&lt;/CodesignEntitlements>
      &lt;/PropertyGroup>
      
      &lt;PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|net8.0-ios|AnyCPU'">
        &lt;CodesignEntitlements>Platforms\iOS\Entitlements-Production.plist&lt;/CodesignEntitlements>
      &lt;/PropertyGroup>
      
      

      It’s important to ensure that both Entitlements.plist and Entitlements-Production.plist files are not included in the build by accident. This can be achieved by setting their Build Action to None:

      1. Right-click on each file (Entitlements.plist and Entitlements-Production.plist) in Visual Studio.
      2. Select Properties.
      3. Set the Build Action to None.

      This step ensures that the files are correctly associated with your app for code-signing purposes but are not compiled into the app bundle unnecessarily.

      (Update) Or you can simply use this approach in your .csproj file which is far more easier:

      	&lt;ItemGroup Condition="$(TargetFramework.Contains('-ios'))">
      		&lt;CustomEntitlements Include="aps-environment" Condition="'$(Configuration)' == 'Release'" Type="String" Value="production" />
      		&lt;CustomEntitlements Include="aps-environment" Condition="'$(Configuration)' == 'Debug'" Type="String" Value="development" />
      	&lt;/ItemGroup>