Sobrescribir implementación original de un Renderer en Xamarin Forms

¡Hola!
Cuando en Xamarin Forms utilizamos algún control de una librería externa, muchas veces (casi siempre) utiliza implementaciones nativas, es decir, lo que típicamente llamamos Renderers. A veces nos ocurre que queremos modificar el comportamiento, extender o aplicar alguna correcciones a esos Renderers, ¿qué hacemos normalmente en estos casos? Creamos un control nuevo en nuestro código compartido que herede del control original y hacemos lo mismo en las diferentes plataformas. Lo vemos en un ejemplo:
Imaginemos que queremos aplicar una corrección al control (disponible a través de NuGet) PullToRefreshLayout. Lo tipico sería, en nuestro código compartido, crear un control personalizado que herede de PullToRefreshLayout:

1
2
3
4
5
6
7
8
9
    using System;
 
    namespace SampleOverrideRenderer
    {
        public class ExtendedPullToRefreshLayout : Refractored.XamForms.PullToRefresh.PullToRefreshLayout
        {
 
        }
    }

El siguiente paso sería crear un renderer para cada una de las plataformas (sólo vamos a ver iOS):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    using SampleOverrideRenderer;
    using SampleOverrideRenderer.iOS;
    using Xamarin.Forms;
 
    [assembly: ExportRenderer(typeof(ExtendedPullToRefreshLayout), typeof(ExtendedPullToRefreshLayoutRenderer))]
    namespace SampleOverrideRenderer.iOS
    {
        public class ExtendedPullToRefreshLayoutRenderer : Refractored.XamForms.PullToRefresh.iOS.PullToRefreshLayoutRenderer
        {
            public ExtendedPullToRefreshLayoutRenderer()
            {
                System.Diagnostics.Debug.WriteLine("Entra en el constructor ExtendedPullToRefreshLayoutRenderer");
            }
        }
    }

Si ahora utilizamos nuestro control en una página, veremos que va a utilizar nuestro ExtendedPullToRefreshLayoutRenderer:

1
2
3
4
5
6
7
8
9
10
11
12
    <?xml version="1.0" encoding="utf-8"?>
    <ContentPage 
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
        xmlns:local="clr-namespace:SampleOverrideRenderer" 
        x:Class="SampleOverrideRenderer.MainPage">
 
        <local:ExtendedPullToRefreshLayout>
            <Label Text="Welcome to XF" VerticalOptions="Center" HorizontalOptions="Center"/>
        </local:ExtendedPullToRefreshLayout>
 
    </ContentPage>

Si vemos el resultado:


Si nos fijamos en las trazas, podemos ver que se está utilizando nuestro Renderer (ExtendedPullToRefreshLayout).
Hasta aquí ningún problema, es la situación habitual que solemos encontrarnos. Pero, ¿qué ocurre si queremos cambiar la implementación del renderer original que utiliza PullToRefreshLayout? Esto puede ocurrirnos por varios motivos, por ejemplo, utilizamos en un montón de sitios el control original (en el ejemplo PullToRefreshLayout) y es demasiado trabajo nos da pereza cambiarlo en todas los sitios donde lo usamos. O estamos haciendo pruebas de los cambios, y antes de cambiar el control original en todos los sitios (que puede llevarnos un buen rato), queremos hacer pruebas por toda la aplicación para ver si los cambios nos convencen..
Pues no nos va a servir simplemente crear nuestro Renderer y utilizar el atributo ExportRenderer (como hicimos antes en ExtendedPullToRefreshLayoutRenderer), es decir, si hacemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    using SampleOverrideRenderer.iOS;
    using Xamarin.Forms;
 
    [assembly: ExportRenderer(typeof(Refractored.XamForms.PullToRefresh.PullToRefreshLayout), typeof(ExtendedPullToRefreshLayoutRenderer))]
    namespace SampleOverrideRenderer.iOS
    {
        public class ExtendedPullToRefreshLayoutRenderer : Refractored.XamForms.PullToRefresh.iOS.PullToRefreshLayoutRenderer
        {
            public ExtendedPullToRefreshLayoutRenderer()
            {
                System.Diagnostics.Debug.WriteLine("Entra en el constructor ExtendedPullToRefreshLayoutRenderer");
            }
        }
    }

Y en nuestra vista esto:

1
2
3
4
5
6
7
8
9
10
11
12
13
    <?xml version="1.0" encoding="utf-8"?>
    <ContentPage 
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
        xmlns:local="clr-namespace:SampleOverrideRenderer" 
        xmlns:refractored="clr-namespace:Refractored.XamForms.PullToRefresh;assembly=Refractored.XamForms.PullToRefresh"
        x:Class="SampleOverrideRenderer.MainPage">
 
        <refractored:PullToRefreshLayout>
            <Label Text="Welcome to XF" VerticalOptions="Center" HorizontalOptions="Center"/>
        </refractored:PullToRefreshLayout>
 
    </ContentPage>

No nos va a funcionar, es decir, no va a utilizar nuestra nueva implementación (ExtendedPullToRefreshLayoutRenderer):


Como vemos, no ha dejado la traza del constructor. El problema está en que no se está sobrescribiendo la implementación original del Renderer:


Ya vemos más claro lo que ocurre con la imagen anterior. Ahora lo que vamos a hacer es registrar a mano nuestra nueva implementación del Renderer, sin utilizar el atributo como hicimos antes, es más, lo podríamos quitar del Renderer (me refiero a esto => [assembly: ExportRenderer(typeof(Refractored.XamForms.PullToRefresh.PullToRefreshLayout), typeof(ExtendedPullToRefreshLayoutRenderer))]). Como digo, lo vamos registrar directamente:

1
2
3
4
5
6
7
8
9
10
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        global::Xamarin.Forms.Forms.Init();
 
        Xamarin.Forms.Internals.Registrar.Registered.Register(typeof(PullToRefreshLayout), typeof(ExtendedPullToRefreshLayoutRenderer)); 
        LoadApplication(new App());
 
        return base.FinishedLaunching(app, options);
    }

Si ahora ejecutamos la aplicación ya funciona correctamente y utiliza nuestro Renderer:


Hay que tener en cuenta que hacer esto es un poco peligroso, si nos fijamos es parte de Xamarin.Internals y, además, la clase Registered está marcada con el atributo [EditorBrowsable(EditorBrowsableState.Never)], en cualquier caso, si lo usamos estaría bien tener algún test que compruebe que no rompemos nada cuando actualicemos a una nueva versión de Xamarin Forms y que se sigue registrando nuestra nueva implementación del Renderer.

¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *