Monkey Conf 2021

Otro año más, y otra edición de la Monkey Conf nueva. Este ao es ya la cuarta edición. Hablamos del mayor evento centrado en desarrollo multiplataforma con C# realizado en España. Una fecha especial para reunir a la comunidad, disfrutar de sesiones técnicas, networking y compartir momentos entre todos.

Monkey Conf 2021

Este año, al igual que el año pasado, debido a la situación con el Covid-19, el formato será online.

La fecha

Este año el evento tendrá lugar entre semana el próximo Miércoles 01 de Diciembre.

De igual forma, al ser el evento online y aunque contar con entradas nos ayudaría a conocer de forma aproximada el interés en el evento, este año no tendremos entradas.

Call 4 Papers

¿Has desarrollado una aplicación con Xamarin?, ¿quieres hablar de .NET MAUI?, ¿Apps en Linux con Avalonia?. El Call 4 Papers del evento ya se encuentra disponible. Este año al ser un evento online nos abre la posibilidad de tener speakers de cualquier lugar del mundo!.

Sponsors

En años anteriores, gracias a sponsors cubríamos costes básicos del evento como goodies (pegatinas, camisetas, etc.), el almuerzo, etc. Este año el evento es online, y seguimos buscando Sponsors.

¿Por qué?.

Creemos que estamos viviendo momentos complicados para todos, y pensamos que gracias a sponsors podríamos sortear y regalar licencias de productos relacionados con el desarrollo, etc. Buscamos tener un pequeño gesto que ayude a alegrar el día a asistentes y esto sería posible gracias a la ayuda de Sponsors.

Si estas interesado en patrocinar el evento, puedes encontrar más información en la página web del evento.

Más información

Utilizando el Windows Subsystem para Android para desarrollar

Si has desarrollado con .NET y Xamarin en los últimos años recordarás la evolución al trabajar con emuladores Android. El paso de los emuladores por defecto a Intel HAXM fue un gran paso adelante en el rendimiento general; luego pasamos a tener más opciones como Xamarin Android Player para finalmente llegar al soporte de Hyper-V lo que facilitó otro salto en rendimiento y tiempo de arranque.

¿Y si estamos ante el siguiente salto?. En este artículo, vamos a conocer el Windows Subsystem para Android así como lo necesario para utilizarlo para desarrollar y depurar aplicaciones Android.

Windows 11

Entre las novedades de Windows 11 nos llega un subsistema de Android.

Windows 11

El objetivo principal es el permitir ejecutar aplicaciones Android en Windows. No existe un launcher Android o similar, Windows es directamente quien se encarga de lanzar las Apps.

Amazon App Store

Como usuarios, aunque no es la única vía, se pueden obtener aplicaciones Android desde la Store utilizando la Amazon App Store.

Instalar el subsistema de Android en Windows 11

Lo primero que debemos hacer es tener una build de Windows Insiders Beta (al momento de escribir este artículo) que coincida con Windows 11 Build 22000.xxx.

Windows 11 Build 22000.xxx

Tras tener esta versión de Windows es hora de instalar el Windows Subsystem for Android que tambien viene con la Amazon App Store.

Este paso es sencillo, solo hay que entrar en la Store, instalar y reiniciar.

Instalar WSA

Hora de desarrollar!

Dentro del WSA App encontrarás bastantes opciones.

Opciones WSA

Necesitamos cambiar varias opciones:

  • Habilitar el modo Continuous para que el subsistema siempre este listo para ser usado.
  • Activar el modo de desarrollador.

NOTA: Al activar el modo de desarrollador veremos las instrucciones necesarias para conectar por adb.

Con el subsistema Windows para Android en ejecución podemos usar 127.0.0.1:58526 para conectar y hacer debugging.

adb connect 127.0.0.1:58526

NOTA: Lo visto anterior es la forma por defecto para conectar con WSA pero puedes encontrar más opciones en la documentación oficial.

Una vez conectado vía adb, sencillamente aparecerá la opción de WSA en Visual Studio.

Visual Studio

Más información

Borders personalizados en .NET MAUI

Aplicar bordes personalizados es una necesidad bastante común utilizada en el diseño de aplicaciones para crear estructuras visuales concretas, resaltar elementos o poder personalizar ciertos controles específicos.

En Xamarin.Forms el control Frame ha sido ampliamente utilizado con este fin. Sin embargo, el control Frame contaba con ciertas limitaciones:

  • No poder personalizar cada esquina del borde.
  • No poder personalizar el ancho del borde.
  • No poder utilizar brushes en el propio borde.
  • Etc.

Con la Preview 9 de .NET MAUI llega un nuevo control, Border, con bastantes más posibilidades y con el objetivo de resolver todas las limitaciones que teníamos hasta ahora. ¿Lo revisamos en este artículo?.

Nuevo control Border

El nuevo control Border, similar en concepto al Frame, es un control que permite un único elemento como contenido pero que puede ser cualquier cosa. Es decir, podremos aplicar un Border desde a un sencillo Label a un Grid con un layout complejo.

<Border Stroke="Red" StrokeThickness="2">
    <Border.ShapeBorder>
        <RoundRectangle CornerRadius="12, 0, 0, 24" />
    </Border.ShapeBorder>
    <Label Text="Border" />
</Border>

El control Border cuenta con diferentes propiedades para personalizar el borde:

  • Stroke: Brush que define el color o gradiente a utilizar en el borde.
  • StrokeThickness: Grosor del borde.
  • StrokeDashArray: Se utiliza una colección de valores Double que indican el patrón Dash y los espacios que se usan para delinear la figura a utilizar en el borde.
  • StrokeDashOffset: Es un valor de tipo Double que indica la distancia a usar en el patrón Dash desde donde comienza.
  • StrokeLineJoin: Especifica como se realizará la unión entre los vértices de la figura a utilizar en el borde.
  • StrokeLineCap: Especifica como es el trazo del inicio y fin de la figura a utilizar en el borde.

Y entre las propiedades disponibles me gustaría destacar, StrokeShape. Espera un Shape que define la forma del borde. Por defecto se usa un Rectángulo pero puede ser desde un Rectángulo con bordes redondeados a cualquier figura.

Podemos pasar de un borde con forma rectangular y bordes redondeados personalizados:

<Border Stroke="Red" StrokeThickness="2">
    <Border.ShapeBorder>
        <RoundRectangle CornerRadius="12, 0, 0, 24" />
    </Border.ShapeBorder>
    <Label Text="Border" />
</Border>

A una elipse:

<Border Stroke="Red" StrokeThickness="2">
    <Border.ShapeBorder>
        <Ellipse />
    </Border.ShapeBorder>
    <Label Text="Border" />
</Border>

A continuación, veamos como funcionan estas propiedades en conjunto en un ejemplo de forma visual.

.NET MAUI Border

¿Qué te parece esta novedad en .NET MAUI?. Recuerda, puedes usar los comentarios de la entrada para dejar tu feedback.

Más información

Sombras en .NET MAUI

La sombra es una de las formas en que un usuario percibe la elevación. Este efecto de elevación transmite en qué debe centrarse el usuario. Y esta es la razón por la que los diseñadores móviles prefieren incorporar sombras en sus diseños.

Con la llegada de la Preview 9 de .NET MAUI obtenemos novedades a nivel de UI como el nuevo control Border o una nueva API para poder crear sombras. En este artículo, vamos a centrarnos en conocer la nueva API de sombras.

Sombras

Sombras

A nivel de View, es decir, en cualquier vista disponible en .NET MAUI, se pueden añadir sombras. Esto quiere decir que se pueden añadir sombras desde en imágenes a cualquier Shape y por supuesto en un Button o Label.

Contamos con una nueva propiedad llamada Shadow de tipo Shadow. Shadow es un Element que cuenta con las siguientes propiedades:

  • Radius: Es el radio del Gaussian blur usado para generar la sombra.
  • Color: El color de la sombra.
  • Offset: Offset de la sombra en relación al elemento visual donde se adjunta la misma.
  • Opacity: La opacidad de la sombra.

La forma más sencilla de añadir una sombra es:

<Label Text="Label">
    <Label.Shadow>
        <Shadow 
            Color="Blue"
            Offset="10, 20"
            Offset="0.5"
            Radius="12">
    </Label.Shadow>
</Label>

NOTA: Cada propiedad que tenemos disponible en la clase Shadow es una BindableProperty por lo tanto, podemos actualizar cada propiedad con bindings.

Podemos crear sombras como recursos compartidos y utilizar la misma sombra en diferentes elementos visuales:

<ContentPage.Resources>
    <ResourceDictionary>

        <Shadow x:Key="Shadow" Brush="Red" Offset="12, 12" Radius="12" />

    </ResourceDictionary>
</ContentPage.Resources>

<Ellipse HeightRequest="100" WidthRequest="100" Fill="Blue" Shadow="{StaticResource Shadow}" />
<Button Text="Button" BackgroundColor="Blue" Shadow="{StaticResource Shadow}" />

Sombras en .NET MAUI

¿Qué te parece esta novedad en .NET MAUI?. Recuerda, puedes usar los comentarios de la entrada para dejar tu feedback.

Más información

Convertir Triggers de Xamarin.Forms a .NET MAUI

Los Triggers permiten expresar acciones de forma declarativa en XAML que cambian la apariencia de los controles en función de eventos o cambios de propiedad. Además, los state triggers, que son un grupo especializado de triggers, definen cuándo se debe aplicar un VisualState.

Como regla general, todos los conceptos relacionados con XAML en Xamarin.Forms funcionarán sin requerir cambios en .NET MAUI.

Xamarin.Forms

<Entry 
    x:Name="Entry"
    Text=""
    Placeholder="Required field" />
<!-- Referenced below in DataTrigger-->
<Button 
    x:Name="Button"
    Text="Save"
    FontSize="Large"
    HorizontalOptions="Center">
    <Button.Triggers>
        <DataTrigger
            TargetType="Button"
            Binding="{Binding Source={x:Reference Entry},
                            Path=Text.Length}"
            Value="0">
            <Setter Property="IsEnabled" Value="False" />
        </DataTrigger>
    </Button.Triggers>
</Button>

.NET MAUI

<Entry 
    x:Name="Entry"
    Text=""
    Placeholder="Required field" />
<!-- Referenced below in DataTrigger-->
<Button 
    x:Name="Button"
    Text="Save"
    FontSize="Large"
    HorizontalOptions="Center">
    <Button.Triggers>
        <DataTrigger
            TargetType="Button"
            Binding="{Binding Source={x:Reference Entry},
                            Path=Text.Length}"
            Value="0">
            <Setter Property="IsEnabled" Value="False" />
        </DataTrigger>
    </Button.Triggers>
</Button>

Convertir Behaviors de Xamarin.Forms a .NET MAUI

Los Behaviors permiten agregar funciones a los controles de la interfaz de usuario sin tener que incluirlos en subclases. En su lugar, la función se implementa en una clase Behavior y se asocia al control como si fuera parte de este.

En .NET MAUI existe exactamente el mismo concepto, permitiendo reutilizar código de forma sencilla.

Veamos un ejemplo.

Xamarin.Forms

using Xamarin.Forms;

namespace Behaviors
{
    public class NumericValidationBehavior : Behavior
    {
        protected override void OnAttachedTo(Entry entry)
        {
            entry.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(entry);
        }

        protected override void OnDetachingFrom(Entry entry)
        {
            entry.TextChanged -= OnEntryTextChanged;
            base.OnDetachingFrom(entry);
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            bool isValid = double.TryParse(args.NewTextValue, out double result);
            ((Entry)sender).TextColor = isValid ? Color.Default : Color.Red;
        }
    }
}

.NET MAUI

using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics;

namespace Behaviors
{
    public class NumericValidationBehavior : Behavior
    {
        protected override void OnAttachedTo(Entry entry)
        {
            entry.TextChanged += OnEntryTextChanged;
            base.OnAttachedTo(entry);
        }

        protected override void OnDetachingFrom(Entry entry)
        {
            entry.TextChanged -= OnEntryTextChanged;
            base.OnDetachingFrom(entry);
        }

        void OnEntryTextChanged(object sender, TextChangedEventArgs args)
        {
            bool isValid = double.TryParse(args.NewTextValue, out double result);
            ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
        }
    }
}

¿Cuál es la diferencia?. El código es exactamente el mismo excepto por un detalle, namespaces.

los namespace de Xamarin.Forms cambian por el n amespace Microsoft.Maui.Controls.

Por otro lado, todos los tipos básicos como: Color, Rectangle o Point ahora estan dispoinles en Microsoft.Maui.Graphics. Por esa razón, y porque usamos colores en este Behavior, también incluimos como cambio el nuevo namespace de Graphics.

Convertir Converters de Xamarin.Forms a .NET MAUI

Los enlaces de datos generalmente transfieren datos de una propiedad de origen a una propiedad de destino y, en algunos casos, de la propiedad de destino a la propiedad de origen. Esta transferencia es sencilla cuando las propiedades de origen y destino son del mismo tipo, o cuando un tipo se puede convertir al otro tipo mediante una conversión implícita. Cuando ese no es el caso, se debe realizar un Converter.

Suponga que desea definir un enlace de datos donde la propiedad de origen es de tipo int pero la propiedad de destino es bool. Desea que este enlace de datos produzca un valor falso cuando la fuente entera sea igual a 0 y verdadero en caso contrario.

Puede hacer esto con una clase que implemente la interfaz IValueConverter.

Al igual que otros conceptos de Xamarin.Forms, los Converters se pueden reutilizar en .NET MAUI sin requerir cambios de código.

Veamos un ejemplo.

Xamarin.Forms

using System;
using System.Globalization;
using Xamarin.Forms;

namespace Converters
{
    public class IntToBoolConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (int)value != 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? 1 : 0;
        }
    }
}

.NET MAUI

using Microsoft.Maui.Controls;
using System;
using System.Globalization;

namespace Converters
{
    public class IntToBoolConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (int)value != 0;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (bool)value ? 1 : 0;
        }
    }
}

¿Cuál es la diferencia?. El código es exactamente el mismo excepto por un detalle, los espacios de nombres.

El espacio de nombres de Xamarin.Forms cambia al espacio de nombres Microsoft.Maui.Controls.

Convertir un Custom Renderer de Xamarin.Forms a un Custom Handler de .NET MAUI

Las interfaces de usuario de Xamarin.Forms y .NET MAUI utilizan los controles nativos de la plataforma de destino, lo que permite que las aplicaciones conserven la apariencia adecuada para cada plataforma. El uso de Custom Renderers en Xamarin.Forms permite a los desarrolladores personalizar la apariencia y el comportamiento de los controles en cada plataforma.

Por diferentes motivos, entre los que podemos destacar mejoras en el rendimiento, más posibilidades de extensibilidad, en .NET MAUI tenemos el concepto de Custom Handler. Es similar al concepto de Custom Renderer, pero diferente en diferentes puntos.

¡Veamos paso a paso cómo convertir un Renderer a un Handler!. Para centrarnos en los conceptos clave de Renderers y Handlers, vamos a crear una Entry personalizado, un control bien conocido. Por otro lado, vamos a usar Android simplemente porque tanto desde Windows como desde macOS puedes lanzar las demos asociadas.

Custom Renderer

Cada control de Xamarin.Forms tiene un Renderer adjunto para cada plataforma que crea una instancia de un control nativo. El proceso para crear un Renderer es el siguiente:

  • Crea un control personalizado de Xamarin.Forms.
  • Consume el control personalizado de Xamarin.Forms.
  • Crea el Custom Renderer para el control en cada plataforma.

Creación del control Entry personalizado

Se puede crear un control personalizado creando una clase que herede de la clase View:

public class CustomEntry : View
{

}

El control CustomEntry se crea en el proyecto de la librería .NET Standard y es simplemente un control para capturar texto. Para personalizar la apariencia y el comportamiento del control podemos agregar BindableProperties y eventos.

public static readonly BindableProperty TextProperty =
    BindableProperty.Create(nameof(Text), typeof(string), typeof(Entry), string.Empty);


public string Text
{
    get { return (string)GetValue(TextProperty); }
    set { SetValue(TextProperty, value); }
}

Consumir el control personalizado

Se puede hacer referencia al control CustomEntry en XAML en el proyecto de la librería .NET Standard declarando un espacio de nombres y usando el prefijo del espacio de nombres en el elemento de control.


    ...
    
    ...

Creación del Custom Renderer en cada plataforma

El proceso para crear la clase del Custom Renderer es el siguiente:

  1. Crea una clase que herede de ViewRenderer que representa el control nativo.
  2. Sobreescribe el método OnElementChanged que representa el control nativo y la lógica para personalizar el control. Este método se llama cuando se crea el control de Xamarin.Forms correspondiente.
  3. Sobreescribe el método OnElementPropertyChanged que responde a cualquier cambio de una BindableProperty.
  4. Agrega el atributo ExportRenderer a la clase del Custom Renderer para especificar que se usará para representar el control Xamarin.Forms. Este atributo se usa para registrar el representador personalizado con Xamarin.Forms (usando Assembly Scanning).

La clase ViewRenderer expone el método OnElementChanged, al que se llama cuando se crea el control Xamarin.Forms para representar el control nativo correspondiente. Este método toma un parámetro ElementChangedEventArgs que contiene las propiedades OldElement y NewElement. Estas propiedades representan el elemento Xamarin.Forms al que se adjuntó el representador y el elemento Xamarin.Forms al que está adjunto el representador, respectivamente. En la aplicación de ejemplo, la propiedad OldElement será nula y la propiedad NewElement contendrá una referencia al control CustomEntry.

El método OnElementChanged en la clase CustomEntryRenderer es el lugar para realizar la personalización del control nativo. Se puede acceder a una referencia del control nativo que se usa en la plataforma a través de la propiedad Control. Además, se puede obtener una referencia al control Xamarin.Forms que se está representando a través de la propiedad Element.

protected override void OnElementChanged(ElementChangedEventArgs e)
{
    base.OnElementChanged(e);

    if (e.OldElement == null)
    {
        EditText editText = new EditText(Context);

        _defaultTextColors = editText.TextColors;
        _defaultPlaceholderColors = editText.HintTextColors;

        SetNativeControl(editText);
    }

    UpdateText();
    UpdateTextColor();
    UpdatePlaceholder();
    UpdatePlaceholderColor();
    UpdateCharacterSpacing();
    UpdateHorizontalTextAlignment();
    UpdateVerticalTextAlignment();
}

La invalidación OnElementPropertyChanged responde a los cambios de BindableProperties enlazadas al control Xamarin.Forms.

protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == CustomEntry.TextProperty.PropertyName)
        UpdateText();
    else if (e.PropertyName == CustomEntry.TextColorProperty.PropertyName)
        UpdateTextColor();
    if (e.PropertyName == CustomEntry.PlaceholderProperty.PropertyName)
        UpdatePlaceholder();
    else if (e.PropertyName == Entry.PlaceholderColorProperty.PropertyName)
        UpdatePlaceholderColor();
    else if (e.PropertyName == Entry.CharacterSpacingProperty.PropertyName)
        UpdateCharacterSpacing();
    else if (e.PropertyName == Entry.HorizontalTextAlignmentProperty.PropertyName)
        UpdateHorizontalTextAlignment();
    else if (e.PropertyName == Entry.VerticalTextAlignmentProperty.PropertyName)
        UpdateVerticalTextAlignment();

    base.OnElementPropertyChanged(sender, e);
}

Nada realmente nuevo hasta ahora, solo una pequeña revisión sobre cómo trabajar con Custom Renderers en Xamarin.Forms, ¿vamos a .NET MAUI?

Pero primero, ¿por qué cambiar de la arqquitectura de Renderers a la de Handlers en .NET MAUI?

Para comprender las motivaciones que impulsan el cambio, incluso conociendo las implicaciones en la cantidad de cambios necesarios, necesitamos saber qué es lo que está mal o es mejorable en los Renderers.

¿Recuerdas el atributo ExportRenderer que usa para registrar el Renderer? Esto le dice a Xamarin.Forms que en el arranque, haciendo uso del escaneo de ensamblados, debe buscar todas las bibliotecas referenciadas y usando este atributo, y si lo encuentra, registra el Renderer. Es fácil de usar, pero… el escaneo del ensamblados es lento y penaliza el inicio.

El método OnElementChanged generalmente causa confusión. ¿Cuándo usar OldElement , y NewElement?, ¿cuándo creo valores por defecto o me suscribo a eventos?. Causar confusión es un problema, pero es un problema aún mayor si no tener una forma fácil de suscribirse/desuscribirse hace que a veces no se anule la suscripción (por ejemplo) y… penaliza el rendimiento.

Todos esos métodos privados para actualizar las propiedades del control nativo son un gran problema. Es posible que debas hacer un pequeño cambio y debido a la falta de acceso (nuevamente, ¡métodos privados aquí y allá!), terminas creando un Custom Renderer más grande de lo necesario, etc.

Resolver estos problemas, y otros menores, es el objetivo fundamental de los Handlers.

Custom Handlers

El proceso para crear la clase del Custom Handler es el siguiente:

  1. Crea una clase que implementa la clase ViewHandler que representa el control nativo.
  2. Sobrecarga el método CreateNativeView que representa el control nativo.
  3. Crea el diccionario Mapper que responda a los cambios de propiedad.
  4. Registra el controlador usando el método AddHandler en la clase Startup.

Creación del control Entry personalizado

Los Handlers utilizan interfaces que derivan de la interfaz IView. Esto evita que el control multiplataforma tenga que hacer referencia a su Handler y que el Handler tenga que hacer referencia al control multiplataforma. El Mapper se encarga de mapear las propiedades multiplataforma a lo nativo en cada plataforma.

De esta forma comenzamos a crear la interfaz que define nuestro control:

public interface ICustomEntry : IView
{
    public string Text { get; }
    public Color TextColor { get; }
    public string Placeholder { get; }
    public Color PlaceholderColor { get; }
    public double CharacterSpacing { get; }
    public TextAlignment HorizontalTextAlignment { get; }
    public TextAlignment VerticalTextAlignment { get; }

    void Completed();
}

Se puede crear un control personalizado heredando de la clase View e implementando la interfaz del control:

public class CustomEntry : View, ICustomEntry
{

}

Crear el Custom Handler cada plataforma

Cree una subclase de la clase ViewHandler que representa el control nativo.

public partial class CustomEntryHandler : ViewHandler
{

}

Parece un cambio trivial, pasamos de heredar de ViewRenderer a ViewHandler, ¡pero es mucho más!.

ViewRenderer en Xamarin.Forms crea un elemento padre, en el caso de Android un ViewGroup, que se usaba para tareas de posicionamiento auxiliares. ViewHandler NO crea ningún elemento padre que ayudea a reducir la jerarquía visual y, por lo tanto, mejora el rendimiento.

Heredando de ViewHandler, tenemos que implementar el método CreateNativeView.

protected override EditText CreateNativeView()
{
    return new EditText(Context);
}

¿Recuerdas que anteriormente revisamos cómo se usó OnElementChanged en Xamarin.Forms?. En este método creamos el control nativo, inicializamos valores predeterminados, nos suscribimos a eventos, etc. Sin embargo, requiere una gran diversidad de conocimientos: qué es OldElement y NewElement, etc.

.NET MAUI simplifica y distribuye todo lo que hicimos anteriormente en el método OnElementChanged en diferentes métodos de una manera más sencilla.

Creamos el control nativo en el método CreateNativeView. Por otro lado, tenemos otros métodos como ConnectHandler y DisconnectHandler.

protected override void ConnectHandler(EditText nativeView)
{
    _defaultTextColors = nativeView.TextColors;
    _defaultPlaceholderColors = nativeView.HintTextColors;

    _watcher.Handler = this;
    nativeView.AddTextChangedListener(_watcher);

    base.ConnectHandler(nativeView);
}

protected override void DisconnectHandler(EditText nativeView)
{
    nativeView.RemoveTextChangedListener(_watcher);
    _watcher.Handler = null;

    base.DisconnectHandler(nativeView);
}

ConnectHandler es el lugar ideal para inicializar, suscribir eventos, etc. y de la misma manera podemos eliminar, cancelar la suscripción de eventos, etc. en DisconnectHandler.

El Mapper

** Mapper ** es un nuevo concepto introducido por los Handlers en .NET MAUI. No es más que un diccionario con las propiedades (y acciones) definidas en la interfaz de nuestro control (recuerda, usamos interfaces en el Handler). Reemplaza todo lo que se hacía en el método OnElementPropertyChanged en Xamarin.Forms.

public static PropertyMapper CustomEntryMapper = new PropertyMapper(ViewHandler.ViewMapper)
{
    [nameof(ICustomEntry.Text)] = MapText,
    [nameof(ICustomEntry.TextColor)] = MapTextColor,
    [nameof(ICustomEntry.Placeholder)] = MapPlaceholder,
    [nameof(ICustomEntry.PlaceholderColor)] = MapPlaceholderColor,
    [nameof(ICustomEntry.CharacterSpacing)] = MapCharacterSpacing,
    [nameof(ICustomEntry.HorizontalLayoutAlignment)] = MapHorizontalLayoutAlignment,
    [nameof(ICustomEntry.VerticalLayoutAlignment)] = MapVerticalLayoutAlignment
};

Mapper mapea propiedades a métodos estáticos.

public static void MapText(CustomEntryHandler handler, ICustomEntry entry)
{
    handler.NativeView?.UpdateText(entry);
}

El Mapper, además de simplificar la gestión de cambios de propiedad (notifica las propiedades al inicializar el control y también, cada vez que cambia una propiedad) nos permite más opciones de extensibilidad.

Por ejemplo:

using Microsoft.Maui; using Microsoft.Maui.Controls;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();

#if __ANDROID__
        CustomEntryMapper[nameof(ICustomEntry.Text)] = (handler, view) =&gt;
        {
            (handler.NativeView as Android.Widget.EditText).Text = view.text + "custom";
        };
#endif
    }
}

Registrar el handler

A diferencia de Xamarin.Forms que usa el atributo ExportRenderer que a su vez hace uso del escaneo de ensamblado, en .NET MAUI el registro del handler es ligeramente diferente.

appBuilder
    .UseMauiApp()
    .ConfigureMauiHandlers(handlers =&gt;
    {
#if __ANDROID__
        handlers.AddHandler(typeof(CustomEntry), typeof(CustomEntryHandler));
#endif
    });

Hacemos uso de AppHostBuilder y el método AddHandler para registrar el Handler. Al final requiere, como en Xamarin.Forms, una línea para indicar que queremos dar de alta el Handler, pero se evita el uso de Assembly Scanning, que es lento y costoso, penalizando el inicio.

.NET MAUI Compatibility: Reutilizar tus Renderers sin cambios!

¿Qué es el paquete de Compatibilidad?

Para hacer transición de Xamarin.Forms a .NET MAUI lo más fluida posible, se agrega el paquete Compatability que agrega la funcionalidad de Xamarin.Forms que permite reutilizar código como Custom Renderers sin necesidad de realizar cambios.

¿Custom Renderers de Xamarin.Forms?

Las interfaces de usuario de Xamarin.Forms se crean mediante controles nativos de la plataforma de destino, lo que permite que las aplicaciones de Xamarin.Forms conserven la apariencia adecuada para cada plataforma. Los Custom Renderers permiten a los desarrolladores utilizar este proceso para personalizar la apariencia y el comportamiento de los controles de Xamarin.Forms en cada plataforma.

Veamos un ejemplo. Vamos a crear un Entry personalizada.

using Xamarin.Forms;

namespace Renderers
{
    public class CustomEntry : Entry
    {

    }
}

La implementación en Android

using Android.Content;
using Renderers;
using Renderers.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;

[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace Renderers.Droid.Renderers
{
    public class CustomEntryRenderer : EntryRenderer
    {
        public CustomEntryRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Control.SetBackgroundColor(global::Android.Graphics.Color.LightGreen);
            }
        }
    }
}

Como podemos ver, solo estamos modificando el color de fondo del control nativo. Algo realmente simple, pero suficiente para el propósito de este documento, para aprender a reutilizar Renderers sin cambiar el código de los mismos.

Reutilizar el Renderer

El Renderer en .NET MAUI usa exactamente el mismo código:

using Android.Content;
using Compatibility;
using Compatibility.Droid.Renderers;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Compatibility;
using Microsoft.Maui.Controls.Compatibility.Platform.Android;

[assembly: ExportRenderer(typeof(CustomEntry), typeof(CustomEntryRenderer))]
namespace Compatibility.Droid.Renderers
{
    public class CustomEntryRenderer : EntryRenderer
    {
        public CustomEntryRenderer(Context context) : base(context)
        {
        }

        protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
        {
            base.OnElementChanged(e);

            if (Control != null)
            {
                Control.SetBackgroundColor(global::Android.Graphics.Color.LightGreen);
            }
        }
    }
}

Los únicos cambios son reemplazar algunos espacios de nombres para usar Microsoft.Maui.Controls.Compatibility.

¿Y ya está todo listo?

No exactamente, en lugar de usar Assembly Scanning, .NET MAUI usa la clase Startup para realizar tareas como registrar Handlers o Renderers.

public class Startup : IStartup
{
    public void Configure(IAppHostBuilder appBuilder)
    {
        appBuilder
            .UseMauiApp<App>()
            .ConfigureMauiHandlers(handlers =>
            {
#if __ANDROID__
                handlers.AddCompatibilityRenderer(typeof(CustomEntry), typeof(Droid.Renderers.CustomEntryRenderer));
#endif
            });
    }
}

.NET MAUI Preview 5

Vaya, no hace tanto del //Build donde se lanzo la Preview 4 y ya tenemos disponible la nueva Preview 5. Se continua con progreso release a release en todas las áreas (implementaciones en el framework, novedades en el framework, proyecto único, tooling, etc). En esta versión tenemos novedades jugosas cómo la llegada de Shell, animaciones o el proyecto único.

En este artículo, vamos a hacer un repaso a todas las novedades principales.

Novedades en controles

Se sigue el proceso de creación de Handlers (recuerda, es la nueva arquitectura para la creación de los controles nativos desde la abstracción) con más controles 100% completados (ActivityIndicator, CheckBox, Image, Slider, Stepper) y varios más completos.

Más y más controles!

Disponible transformaciones y animaciones

Se ha añadido implementación para todas las propiedades que permiten aplicar transformaciones a un control (rotar, escalar, etc). De igual forma, se añade soporte a animaciones en controles de .NET MAUI.

Animaciones!

Tanto el aplicar transformaciones, como el uso de animaciones mantiene la misma API de Xamarin.Forms:

view.FadeTo(1, 800);

Así que, si conocías la API o tenías código relacionado con animaciones, podrás portar código así como reutilizar tanto código como conocimientos en .NET MAUI.

Novedades en Proyecto único

Si habías probado el proyecto único, verías que todo estaba unificado en un único proyecto para Android, iOS y macOS pero teníamos otros dos proyectos para WinUI. Ahora, con la Preview 5, al realizar:

dotnet new maui

Se creará una solución con dos proyectos, uno haciendo uso de multi targeting para la mayoría de plataformas, y otro para WinUI. Esto es otro paso adelante en el concepto de proyecto único y sobretodo evitará muchas confusiones acerca de qué proyecto usar para lanzar la App en Windows haciendo uso de WinUI.

NOTA: Para poder tener soporte a esta nueva estructura de proyectos, necesitas tener instalado las extensiones de Visual Studio Project Reunion 0.8 (Preview).

Disponible en NuGet

Por primera vez no es necesario añadir unas fuentes de NuGet disponibles porque .NET MAUI ya esta disponible en nuget.org.

nuget.org

Primera documentación disponible!

En esta Preview es donde recibimos también la primera documentación oficial disponible en https://docs.microsoft.com/es-es/dotnet/maui/

Documentación

Visual Studio 2020 Preview 1

Junto con la Preview 5 de .NET 6 llega la primera Preview de Visual Studio 2022!.

Primera Preview de Visual Studio 2022

Con soporte a 64 bits, mejoras de rendimiento, mejoras en IntelliCode y bastantes más novedades, también añade soporte a desarrollo móvil.

NOTA: Si desarrollas con .NET MAUI, por ahora deshabilita XAML Hot Reload para evitar errores (en desarrollo).

Más información