In this session we are going to see in depth the new .NET MAUI handlers, a comparison with the Xamarin.Forms renderers as well as other related aspects such as the possibilities when extending or customizing a handler, performance, etc.
5. – .De .NET 5 a .NET 6
.NET Framework
Mono / Xamarin
.NET Core.NET
Un SDK, un BCL, herramientas unificadas
Mobile & Desktop Cross-platform UI nativa
UI web Cross-platform
Investigaciones en la nube
Continuar mejorando la velocidad, tamaño, diagnóstico en
servicios Azure
La vision de un .NET
6. UI nativa multiplataforma
Proyecto único, base de código única
Implementar en múltiples dispositivos, móviles y de
escritorio
Evolución de Xamarin.Forms
Dirigido a .NET 6, disponible a finales del próximo año
Crear interfaces de usuario atractivas para cualquier dispositivo
La evolución de Xamarin.Forms
.NET MAUI
.NET Platform
7. .NET MAUI
Proyectos SDK-style
Proyecto único
Soporte CLI
.NET 6 BCL
Multi-paradigma
• Interfaz de usuario compatible con XAML y C#
• Listo para Blazor y C# MVU
DESKTOP MOBILE
Windows
macOS
iOS
Android
.NET MAUI
File | New
• Multi-platform App UI (.NET)
CLI
• dotnet install maui
• dotnet new maui
Namespaces
• System.Maui (previously Xamarin.Forms)
• System.Device (previously Xamarin.
9. La arquitectura de .NET MAUI
Las API de Android, iOS, macOS y Windows están unificadas en una API abstracta que permite una experiencia
de desarrollo que permita escribir una vez y ejecutar en cualquier plataforma, al tiempo que proporciona un
acceso total a todos los aspectos de cada plataforma nativa.
App Code interactúa principalmente con .NET
MAUI API (1).
Según sea necesario, App Code puede acceder
directamente las API de la plataforma (2) a través
Handlers, etc.
.NET MAUI accede directamente las API de la
plataforma nativa (3).
Android iOS macOS Windows
Xamarin.Android Xamarin.iOS Xamarin.Mac WinUI
.NET MAUI
App Code
Mono Runtime WinRT
.NET 6 BCL
1
2
3
14. ¿Cuáles son los problemas de arquitectura de Xamarin.Forms?
Xamarin.Forms.Button
- Los Renderers estan muy acoplados a componentes Xamarin.Forms
[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomRenderer.iOS {
public class MyEntryRenderer : EntryRenderer
- Assembly Scanning es realmente lento
- Realmente dificil de acceder a la plataforma nativa desde código xplat
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
boxView.On<iOS>().UseBlurEffect(BlurEffectStyle.ExtraLight);
15. Xamarin.Forms: Añadir un comportamiento personalizado, podría ser más sencillo
// You need to know to export this renderer and tie it to a core type…
[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomRenderer.iOS
{
// You need to know what type to inherit from…
public class MyEntryRenderer : EntryRenderer
{
// You need to know what method to override…
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
// You need to know when to do your work (before? after?)
base.OnElementChanged (e);
// You need to know that we call the native view “Control”
// Spoiler alert: this varies from platform to platform and
// sometimes it even varies from renderer to renderer!
if (Control != null) {
// Finally! We can change the color!
public class MyEntry : Entry
{
}
16. Los objetivos con .NET MAUI
- Desacoplar los Renderers de Xamarin.Forms
IButton
17. Los objetivos con .NET MAUI
- Mejorar el rendimiento es uno de los objetivos principales
- Desde el proyecto xplat, registre fácilmente cualquier handler personalizado
RegistrarHandlers.Handlers.Register<Xamarin.Forms.Label, LabelHandler>();
#if MONOANDROID
RegistrarHandlers.Handlers.Register<Xamarin.Forms.Label,
Xamarin.Forms.Platform.Android.LabelRenderer>();
RegistrarHandlers.Handlers.Register<Xamarin.Forms.Button,
Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer>();
#endif
- No hacer assembly scanning
18. Los objetivos con .NET MAUI
- Permitir acceder a código nativo con facilidad
using Xamarin.Forms.PlatformConfiguration;
using Xamarin.Forms.PlatformConfiguration.iOSSpecific;
boxView.On<iOS>().UseBlurEffect(BlurEffectStyle.ExtraLight);
[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomRenderer.iOS {
public class MyEntryRenderer : EntryRenderer
- Eliminar APIs confusas
#if MONOANDROID
ViewHandler.ViewMapper[nameof(IView.BackgroundColor)] = (handler, view) =>
(handler.NativeView as
AView).SetBackgroundColor(Xamarin.Forms.Color.Purple.ToNative());
var textView = crossPlatformLabel.Handler.NativeView as TextView;
#endif
19. Los Renderers actuales
Fundamentalmente es un control nativo
Implementa IVisualElementRenderer
public interface IVisualElementRenderer
{
VisualElement Element { get; }
VisualElementTracker Tracker { get; }
AView View { get; }
event EventHandler<VisualElementChangedEventArgs> ElementChanged;
event EventHandler<PropertyChangedEventArgs> ElementPropertyChanged;
SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint);
void SetElement(VisualElement element);
void SetLabelFor(int? id);
void UpdateLayout();
}
22. La estrategia actual con Xamarin.Forms Renderers
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs
e)
{
if (e.PropertyName == Button.TextColorProperty.PropertyName)
UpdateTextColor();
else if (e.PropertyName == VisualElement.IsEnabledProperty.PropertyName)
UpdateEnabled();
else if (e.PropertyName == Button.FontProperty.PropertyName)
UpdateFont();
else if (e.PropertyName == Button.CharacterSpacingProperty.PropertyName)
UpdateCharacterSpacing();
base.OnElementPropertyChanged(sender, e);
}
private void UpdateFont()
private void UpdateTextColor()
23. La estrategia con Handlers en .NET MAUI
public partial class LabelHandler : AbstractViewHandler<ILabel, TextView>
{
protected override TextView CreateView() => new TextView(Context);
public static void MapText(IViewHandler handler, ILabel view) =>
(handler as LabelHandler).TypedNativeView.UpdateText(view);
}
public static class TextViewExtensions
{
public static void UpdateText(this TextView textView, IText text)
{
textView.Text = text.Text;
}
public partial class LabelHandler
{
public static PropertyMapper<ILabel> LabelMapper =
new PropertyMapper<ILabel>(ViewHandler.ViewMapper)
{
[nameof(ILabel.Text)] = MapPropertyText,
24. Los Handlers son “solo” una reorganización
public static class TextViewExtensions
{
public static void UpdateText(this TextView textView, IText text)
{
textView.Text = text.Text;
}
public class LabelRenderer : ViewRenderer<Label, TextView>
{
void UpdateText()
{
textView.Text = text.Text;
}
25. Xamarin.Forms: Personalizar el comportamiento de un control
// You need to know to export this renderer and tie it to a core type…
[assembly: ExportRenderer (typeof(MyEntry), typeof(MyEntryRenderer))]
namespace CustomRenderer.iOS
{
// You need to know what type to inherit from…
public class MyEntryRenderer : EntryRenderer
{
// You need to know what method to override…
protected override void OnElementChanged (ElementChangedEventArgs<Entry> e)
{
// You need to know when to do your work (before? after?)
base.OnElementChanged (e);
// You need to know that we call the native view “Control”
// Spoiler alert: this varies from platform to platform and
// sometimes it even varies from renderer to renderer!
if (Control != null) {
// Finally! We can change the color!
public class MyEntry : Entry
{
}
26. Maui: Personalizar el comportamiento de un control
// You don’t need to register a new renderer.
public partial class EntryRenderer
{
// You know what method to call because you named it!
public static void MapBackgroundColor (IViewRenderer renderer, IView view)
{
// You don’t need to call any base methods here or worry about order.
// Every renderer is consistent; you know where the native view is.
var nativeView = (NativeView)renderer.NativeView;
var color = view.BackgroundColor;
if (color != null) {
// Phew! That was easy!
nativeView.BackgroundColor = UIColor.FromRGB (204, 153, 255);
}
}
}
public partial class EntryRenderer {
public static PropertyMapper<IView> ViewMapper = new
PropertyMapper<IView> {
// Add your own method to map to any property
[nameof(IView.BackgroundColor)] = MapBackgroundColor
};
}
27. Maui: Personalizar el comportamiento de un control
#if MONOANDROID
ButtonRenderer.ButtonMapper[nameof(IButton.TextColor)] = (handler, view) =>
{
var nativeView = handler.NativeView as Aview;
// Add your own logic to map the TextColor property
};
#endif
31. Renderer to Handler &
Handler to Renderer
Shims
https://github.com/xamarin/Xamarin.Forms/pull/12546
32. .NET MAUI e inyección de dependencias
.NET MAUI usará el concepto de Host similar a ASP.NET. Esto permitirá usar Containers y providers
para nuestros handlers al igual que permitir configuraciones con archivos appsettings, y otras
posibilidades como gestionar el ciclo de vida de cada plataforma, registrar servicios, logging
extensions etc.
El AppBuilder estará oculto por defecto, pero se podrá sobreescribir y usarlo para extender y registrar
servicios, configurar logging o registrar nuevos handlers.
En .NET MAUI, vamos a usar Microsoft Dependency Injection para permitir registrar custom handlers.
var (host,app) = App.CreateDefaultBuilder()
.RegisterHandler<IButton, CustomHandlers.CustomPinkTextButtonHandler>()
.ConfigureServices(ConfigureExtraServices)
.Init<MyApp>();
var page = app.GetStartup()
https://github.com/xamarin/Xamarin.Forms/pull/12460
33. ¿Y que ocurre con los renderers de Xamarin.Forms?
En Xamarin.Forms se usaba el atributo ExportRenderer para registrar un Renderer. Internamente en Xamarin.Forms, se usa el escaneo de
ensamblados para buscar y registrar los Renderers.
En .NET MAUI, el escaneo de ensamblado no se usará por defecto, y permitiremos registrar un Renderer de esta manera:
Sin embargo, permitiremos en los casos deseados registrar Renderers usando el escaneo de ensamblaje:
var (host,app) = App.CreateDefaultBuilder()
.RegisterRenderer<CustomBoxView, CustomBoxViewRenderer>()
.ConfigureServices(ConfigureExtraServices)
.Init<MyApp>();
var page = app.GetStartup()
var (host,app) = App.CreateDefaultBuilder()
.ConfigureRenderers( options => options.ScanForRendererAttribute = true)
.ConfigureServices(ConfigureExtraServices)
.Init<MyApp>();
var page = app.GetStartup()
34. Usando los Renderers “antiguos” en .NET MAUI
Puedes elegir la que más te convenga en cada caso, pero ten en cuenta
que:
Usando Handlers:
• Requiere "algo de tiempo" para la conversión de código.
• Cada Handler es equivalente a un Fast Renderer además de ser
gestionado de forma diferente por Layouts que tienen un mejor
rendimiento.
• Permita más opciones de extensibilidad.
Usando Renderers:
• No requiere cambios.
• No aprovecha ninguna de las mejoras de los Handlers (rendimiento,
extensibilidad, etc.)
36. La arquitectura de .NET MAUI
.NET MAUI utiliza Layouts multiplataforma, interfaces para definir controles, métodos de extension con las
vistas nativas e implementaciones usando vistas nativas. Se utilizan los mismos controles nativos
independientemente de MVVM, MVU, RxUI, Blazor, etc.
Interfaces
System.Maui.Views son interfaces que describen
cada control.
IButton > IText > IView
Handlers
Estos son las abstracciones e implementaciones de
cada control sin conocimiento de MVU o MVVM.
System.Maui.Handlers:
• ButtonHandler.Android.cs
• ButtonHandler.Mac.cs
• ButtonHandler.Standard.cs
• ButtonHandler.Win32.cs
• ButtonHandler.cs
• ButtonHandler.iOS.cs
Patrones
Estas implementaciones de controles añaden soporte a los
patrones de diseño soportados.
System.Maui.Bindables contiene las capacidades relacionadas con
MVVM, y controles con soporte a databinding.
37. El trabajo con .NET MAUI Handlers
• Nueva arquitectura de Renderers
• Nueva metodología
• Nueva organización
• Nueva arquitectura de Layouts
• Eliminar obsoletos
• Eliminar “slow” renderers
• Eliminar pre-AppCompat
• Unificar APIs entre Xamarin.Forms y
Xamarin.Essentials
• Usar
Microsoft.Extensions.DependencyInjection
• Cross-Platform Lifecycle
• Corregir “AndExpand”
• Corregir “MinHeightRequest” & similares
• Usar GlideX
Migration Tool con try-convert
Xamarin Native -> .NET 6
Xamarin.Forms -> .NET MAUI
38. • .NET 5.0 se ha lanzado en Noviembre 2020
• Xamarin.Forms 5.0 en Diciembre 2020
• .NET 6.0 MAUI + soporte a móvil previews con .NET 6 Preview 1
El roadmap de .NET
Dic 2019
.NET Core 3.1
LTS
Nov 2020
.NET 5.0
Nov 2021
.NET 6.0
LTS
Nov 2022
.NET 7.0
Nov 2023
.NET 8.0
LTS
Animated slide
Our vision for one .NET is to simplify the platform and choices for .NET developers and provide a single stack that supports the best of breed solutions for all modern workloads.
Last year at Build, we laid out our vision for one .NET starting with .NET 5. We said we would take .NET Core and Mono/Xamarin implementations and unify them into one base class library (BCL) and toolchain (SDK). In the wake of the global health pandemic, we have had to adapt to the changing needs of our customers and provide the support needed to assist with smooth operations. Our efforts continue to be anchored in helping our customers address their most urgent needs. As a result, we expect these features to be available in preview for the .NET 5 release but the unification will be truly completed with .NET 6, our Long-Term Support (LTS) release. Our vision has not changed, but our timeline has.
.NET 5 will have several cloud & web investments, such as smaller, faster, single file EXEs that use less memory which are appropriate for microservices and containerized applications across operating systems. We will continue to build on the work we have done.
We are still committed to one .NET platform and delivering a quality .NET 5 release to our millions of users in November this year. You will continue to see a wave of innovation happening with multiple previews along the way on the journey to one .NET.
I’m also excited to announce .NET Multi-platform App UI, or just .NET MAUI.
<CLICK>
It is a cross-platform, native UI stack so you’ll get great performance on any device.
<CLICK>
It will allow you to build these apps for any device from a single codebase and project system
<CLICK>
And that includes desktop and mobile across operating systems, like Windows, MacOS, iOS and Android.
<CLICK>
This is the evolution of Xamarin technology, building on Xamarin.Forms and expanding that to cross-platform desktop scenarios.
<CLICK>
It will be part of the unified .NET in the .NET 6 timeframe with previews available end of this year. You’ll see us working on it in the open on GitHub.
In Xamarin.Forms, the Renderers are tightly couple to Xamarin.Forms components.
In the ButtonRenderer, have several references to a Xamarin.Forms Button which is a bindable object implementing INotifyPropertyChanged, etc.
The other topic is, the Xamarin.Forms Registrar where register the available renderers, etc. In this case, use assembly scanning and it is really slow.
What happens is basically, Xamarin.Forms goes through and scans all your assemblies for assembly attributes and then automatically registers to the framework, and
Allow to use that components.
This is great because is easy to use, and try to remove some requirements from the user, but impact in the performance.
The third topic is, in some cases is really complex to reach the native platform from cross platform code.
Mainly, because the way that dependency tree works. The button renderer depends on button component so, the button has no concept of a renderer.
When you are in the Button context, have no context or reference to the renderer.
If you want to Access the native layer, you have different options. You can register your own custom renderer and interact with it or, use an API, that is great but very difficult to discover. I am talking about Platform Specifics. At the end, is not doing anything really special, just setting a cross platform pproperty to a native UI control property.
Relying on event handlers to orchestrate behavior and property changes
Lots of boilerplate code
Tons of knowledge required about order of events, both on the platform side and on the Xamarin.Forms side (i.e,, Does this need to happen on ElementChanged? What about ElementPropertyChanged? Etc.)
The main goal with .NET MAUI is to decouple the renderers. The basic idea is invert the dependency tree.
Now, the Xamarin.Forms Button depends on Ibutton and that interface is what is used by the Button and the ButtonHandler only knows about an IButton.
The Handler doesnt know nothing about the Button component.
This allow you to map different type of controls, because use the interface and have not any concept about what is .NET MAUI.
There are several ideas driving this, but one of the most important ones is performance minded and simplified.
Achieve a much better performance and allow to understand how everything Works in a easy way. Mostly, if you are a new developer. In Xamarin.Forms is a Little bit hard to understanding how to get the renderer, the lifecycle, etc.
Also, is complex as we mentioned to Access to any native element from the cross platform layer.
So, what we are doing is sort of moving everything into the cross-platform side to allow you Access things within the context of your shared application.
So you can see in the slides some samples, and as you can see, the idea is to allow register any handler you want. For sure, there are handlers registered by defaut, but, you can swap those in and out. The idea behind this is, we are not going to use assembly scanning by default. In next slides we will see that we will allow the use of scanning assembly for a very specific case.
This is for teo reason; first one, to gain performance and second one; to simplify the registration process.
Of course, the code sample from the slides, also Works with multitargeting so you can include it in cross platform code.
Ok, let’s continue talking about the goals of .NET MAUI.
Removing confusing APIs and this particular example refers again to Platform Specific.
So, the main idea is, you will have Access to native bits from a cross-platform standpoint. If I have a .NET MAUI Label, I can just Access the native View from cross platform context. You can use compilation directives or créate a platform specific file, then Access the native control and customize, modify whatever you want.
So, again, that is possible because we inverted the inversion as we said earlier, now .NET MAUI taking dependency on the renderes opposed the renderers taking dependency on Xamarin.Forms.
Let’s see the current Xamarin.Forms Renderers implementation. Is an IVisualElementRenderer.
This is what drives all the renderers at the native level. This is how the Layout system interact with the native controls, etc.
Can get basic information about what size should use, when should update a property, etc.
Ok, let’s see the changes between Xamarin.Forms and .NET MAUI.
IVisualElementRenderer is the Xamarin.Forms stuff, and the new one is IViewHandler.
If you do a quick comparison, are very close, or very much the same. Is a new interface that allow to represent controls using the new implementation, but no crazy changes, are close.
For example: SetView is SetElement, UpdateValue is mostly the same as ElementPropertyChanged, etc.
Again, for different points, but mostly again for the performance topic, we are removing eventing.
SetFrame is a Little bit more powerful in respect Xamarin.Forms, because we are applying some important changes in Layouts and want to be more aggresive projecting the sizing information to avoid some unnecesary measurements, etc.
IView is the interface that any cross platform UI element will implement.
For example: IButton.
IViewHandler is the interface that any Handler will implement.
Now, I am going to compare the ways renderers strategy Works in Xamarin.Forms and .NET MAUI.
In Xamarin.Forms, there are some huge if statements, some consistency problems, all of the methods to update any property are privates.
The .NET MAUI strategy is more function based. The idea here is that, all the update methods we have seen before, will be moved to extensión methods.
Why?. Will be public methods that you will be able to apply in your own handlers.
Also, is very powerful for cross-platform, because since the way everything Works is créate a standard API across all platforms, for example, the LabelExtensions, you can use it from a cross platform class, and you can say, handler, UpdateText and in every platform will use the same method with the correct implementation for the platform. So, image this, and we will see this in samples more later, but can have just one class to implement the Handler while in Xamarin.Forms we used several projects, several clases, etc.
Finally, the mapper is just a dictionary of functions, so before when you saw the Xamarin.Forms property changed stuff, what was doing is just mapping string with methods. And is what the mapper is doing but standarizing that to use a structure and something much more pluggable.
So, a quick summary before jump to code. The Handlers are just a reorganization, we are creating some new interfaces to allow some basic changes in how the renderers are created and how the layouts manage the native views; and also créate the mapper concept to allow more options to extend, and finally; move the logic used to update native views to extensions methods.
We are taking all the renderers and converting them to créate a more consistent API.
Relying on event handlers to orchestrate behavior and property changes
Lots of boilerplate code
Tons of knowledge required about order of events, both on the platform side and on the Xamarin.Forms side (i.e,, Does this need to happen on ElementChanged? What about ElementPropertyChanged? Etc.)
You don’t actually have to make this specific to iOS. You could actually make an extension method that changes the background color for each platform, and this mapping can live in one shared partial class…even less code!
Way easier to unit test, much less specific knowledge required about the inner workings of Maui to do something simple as change a color.
Button can be registered to either a Handler or a Renderer and the registrar will react accordingly. The ContentPage calls CreateRenderer on its content which will return an IVisualElementRenderer. If the Content is of type IViewHandler then it gets wrapped with a HandlerToRendererShim so that ContentPage can still render the control successfully.
Predictable releases helps everyone, not only the businesses that use .NET but also the open source projects that are built upon .NET.