Introducción

El pasado 1 de mayo de 20024 acabó oficalmente el soporte de Xamarin Forms por lo que dicho framework no volverá a tener ninguna actualización de seguridad o corrección de errores por lo que el paso “natural” es migrar nuestros proyectos a .NET MAUI. Voy a intentar enumerar las novedades que para mí son más importantes de .NET MAUI respecto a Xamarin Forms.

Me refiero a natural en cuanto a no tener que cambiar de stack tecnológico en los diferentes equipos de desarrollo y que podamos contar con la mayor base de código posible.

Nuevo concepto: Workload. ¿Qué es?

En .NET, un “workload” es un conjunto de herramientas y bibliotecas que se instalan y configuran juntos para permitir el desarrollo de aplicaciones específicas. Los workloads están diseñados para simplificar la instalación y configuración de entornos de desarrollo para diferentes tipos de proyectos.

Existen diferentes workloads por ejemplo para para desarrollar aplicaciones web o aplicaciones móviles y cada uno de ellos incluye todos los componentes necesarios para desarrollar y compilar el tipo de aplicación específico.

Según la documentación oficial, los beneficios de los workloads son:

  • Facilidad de instalación: Instalan todo lo necesario de una sola vez, evitando la necesidad de buscar e instalar componentes individuales.
  • Consistencia: Garantizan que todos los desarrolladores de un equipo tienen las mismas herramientas y versiones.
  • Gestión de dependencias: Simplifican la gestión de dependencias, asegurando que todos los componentes son compatibles entre sí.

Los workloads se instalan de forma global en un equipo de desarrollo, por lo que una vez instalado estará disponible en cualquier proyecto sin importan en que carpeta/ directorio estés trabajando.

En este caso, el comando para instalar .NET MAUI sería:

dotnet workload install maui

Plataforma unificada y simplificación de la estructura del proyecto

En .NET MAUI tenemos un sólo proyecto con directorios específicos para cada plataforma que queramos desplegar y se simplifica el manejo de recursos y archivos específicos siendo estos compartidos estando “centralizados” en el mismo proyecto, por ejemplo, tenemos en el mismo proyecto los archivos de fuentes, imágenes.

AwesomeApp
├── Platforms
│   ├── Android
│   └── iOS
├── Resources
│   ├── Fonts
│   ├── Images
│   └── Raw
├── MauiProgram.cs
└── MyMauiApp.csproj

Respecto a nuestras dependencias de terceros (paquetes nuget), estas se encuentran en el csproj común y no en los csproj de cada plataforma como sucedía en Xamarin Forms.

¿Cómo podemos configurar nuestro proyecto de .NET MAUI?

Para configurar nuestro proyecto de .NET MAUI podemos hacerlo a través del archivo MauiProgram.cs donde podremos configurar nuestras dependencias, servicios, etc.

Me refiero a MauiProgram a nuestra clase estática que será utilizada por cada una de las plataformas que queramos desplegar, y que sobreescribirá el método CreateMauiApp que será el encargado de inicializar nuestra aplicación en .NET MAUI.

En el caso de iOS se hará desde AppDelegate (implementa MauiUIApplicationDelegate) y en el caso de Android desde MainApplication (implementa MauiApplication).

//iOS
[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}

//Android
[Application]
public class MainApplication : MauiApplication
{
    public MainApplication(IntPtr handle, JniHandleOwnership ownership)
        : base(handle, ownership)
    {
    }

    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();
}
  • Registrar nuestros “servicios” en el contenedor de dependencias y podemos hacerlo como:
    • Transient
      • Se crea una nueva instancia del servicio cada vez que se solicita
      • Ideal para servicios ligeros, sin estado.
    • Singleton
      • Se crea una única instancia del servicio la primera vez que se solicita y la misma instancia se usa en todas las solicitudes posteriores.
      • Adecuado para servicios que mantienen un estado compartido o tienen una configuración costosa de crear.
    • Scoped
      • Se crea una nueva instancia del servicio por cada “alcance” o “scope”.

        En las aplicaciones web, un “scope” generalmente es una request pero en aplicaciones de MAUI, el “scope” no es tan claro ya que no hay un equivalente directo.

    Nuestros “servicios” puede ser también nuestros ViewModels, Views o Routing o cualquier artefacto que necesitemos resolver el tiempo de ejecución.

    builder.Services.AddTransient<INavigationService, NavigationService>();
      
    builder.Services.AddTransient<LoginViewModel>();
    builder.Services.AddTransient<LoginView>();
    
    Routing.RegisterRoute(nameof(LoginViewModel), typeof(LoginView));
    
  • Configurar plugins y librerías externas.

    Inicializar y configurar cualquier plugin o librería que la aplicación necesite, el ejemplo más común puede ser el uso de Community Toolkit para MAUI.

    builder.UseMauiCommunityToolkit();
    
  • Configurar handlers personalizados.

    En MAUI la forma recomendada de personalizar nuestros controles es a través de Handlers y es aquí el lugar para registrarlos.

    // Remove Entry control underline (Android)
    Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping("NoUnderline", (handler, view) =>
    {
        handler.PlatformView.BackgroundTintList = Android.Content.Res.ColorStateList.ValueOf(Android.Graphics.Color.Transparent);
    });
    
  • Configurar logging y telemetría.
  • Configurar ajustes específicos de cada plataforma.

    events.AddAndroid(platform =>
    {
        platform.OnCreate((activity, _) =>
        {
            Firebase.FirebaseApp.InitializeApp(activity);
        });
    });
    
  • Configurar nuestras fuentes.

    Si te da por ejecutar la plantilla de MAUI por defecto de una app en un macOS verás como a pesar de que cambies el FontStyle a por ejemplo Bold no se reflejará y es porque en dicha plantilla sólo se incluyen las fuentes Regular y Semibold de OpenSans.

¿Cómo podemos añadir nuget específicos para cada plataforma?

A la hora de añadir el paquete nuget en nuestro csproj deberemos especificar el TargetFramework.

<!-- Shared nuget packages -->
<ItemGroup>		
  <PackageReference Include="Microsoft.Maui.Controls" Version="8.0.7" />
</ItemGroup>

<!-- Android nuget packages -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'">
  <PackageReference Include="Xamarin.Firebase.Config" Version="121.4.0.2" />
</ItemGroup>

<!-- iOS nuget packages -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0-ios'">
  <PackageReference Include="Xamarin.Firebase.iOS.RemoteConfig" Version="8.10.0.3" />
</ItemGroup>

¿Cómo podemos escribir código específico para cada plataforma?

Si por ejemplo tenemos una abstracción en nuestro código compartido y la implementación específica depende de la plataforma podemos añadir dicha implementación en el directorio específico de la plataforma (Platforms/Android o Platforms/iOS).

AwesomeApp
├── Platforms
│   ├── Android
│     ├── MyAndroidImpletantionService.cs
│   └── iOS
│     ├── MyiOSImpletantionService.cs
├── Services
│     ├── IMyCustomService.cs
├── MauiProgram.cs
└── MyMauiApp.csproj

Rendimiento

Xamarin Forms utiliza Custom Renders personalizados para cada plataforma lo cual a veces es algo complejo y a nivel de rendimiento dejaba bastante que desear aunque para pequeños retoques estéticos teníamos disponibles los Effects.

Ahora en .NET MAUI contamos con los Handlers que son más flexibles, ligeros y eficientes lo que hace que el rendimiento sea mayor y se tenga un menor consumo de memoria.

App Lifecycle

En Xamarin Forms la configuración y el manejo de ciclo de vida era específica para cada plataforma.

En .NET MAUI se ha unificado la API del ciclo de vida y se ha mejorado el manejo de eventos y configuración de la aplicación desde un único punto.

Evento Descripción Evento Android Evento iOS
Created Este evento se genera después de crear la ventana nativa, es posible que la ventana aún no esté visible OnPostCreate FinishedLaunching
Activated Este evento se genera cuando se ha activado la ventana OnResume OnActivated
Deactivated ste evento se genera cuando la ventana ya no es la ventana activa OnPause OnResignActivation
Stopped Este evento se genera cuando la ventana ya no está visible OnStop DidEnterBackground
Resumed Este evento se genera cuando una aplicación se reanuda después de detenerse OnRestart WillEnterForeground
Destroying Este evento se genera cuando se destruye y desasigna la ventana nativa OnDestroy WillTerminate

Plataformas soportadas

Xamarin Forms originalmente soportaba iOS, Android y UWP y con un soporte bastante limitado para macOS.

Con .NET MAUI soporta iOS, Android, macOS y Windows y en principio puede admitir más plataformas en un futuro (sólo el tiempo dirá si esto es así).

  • iOS 11 o superior
  • Android 5 (API 21) o superior
  • macOS 10.15 o superior
  • Windows 11 y Windows 10 versión 1809 o posterior.

Enlaces de interés

Info Enlace
.NET Multi-platform App UI (.NET MAUI) https://learn.microsoft.com/es-es/dotnet/maui/what-is-maui?view=net-maui-8.0
.NET Multi-platform App UI (.NET MAUI) Community Toolkit documentation https://learn.microsoft.com/en-us/dotnet/communitytoolkit/maui/
.NET Multi-platform App UI (.NET MAUI) Community Toolkit GitHub https://github.com/CommunityToolkit/Maui