[Xamarin.Forms] Utilizando MvvmCross

Introducción

A la hora de desarrollar aplicaciones multiplataforma con Xamarin una de las arquitecturas más utilizadas sin duda alguna es MVVM. Para realizar la implementación contamos con diferentes opciones y algunos frameworks como MvvmCross, una de las opciones más utilizadas con Xamarin Classic.

Tras recibir diferente feedback, muchos tienen la duda…¿y con Xamarin.Forms?.

En este artículo vamos a ver como utilizar MvvmCross con Xamarin.Forms tanto para acciones básicas como asociar vistas con viewmodels así como realizar la navegación entre páginas o el uso de plugins.

¿Lo vemos?

MVVM

Model-View-ViewModel (MVVM) es un patrón de diseño de aplicaciones que permite desacoplar el código de interfaz de usuario del código que no sea de interfaz de usuario.

El patrón MVVM consta de 3 partes:

  • La vista (View) contiene la interfaz de usuario y su lógica.
  • El vista-modelo (ViewModel) contiene la lógica de presentación.
  • El modelo (Model) contiene la lógica de negocio junto a los datos.

La vista interactúa con el vista-modelo mediante enlace a datos (Data Binding) y mediante comandos:

Las ventajas conseguidas utilizando el patrón son significativas:

  • Nos permite dividir el trabajo de manera muy sencilla (diseñadores – desarrolladores)
  • El mantenimiento es más sencillo.
  • Permite realizar Test a nuestro código.
  • Permite una más fácil reutilización de código.

MvvmCross

MvvmCross es un framework que permite aplicar el patrón MVVM, totalmente centrado en Xamarin y el ecosistema móvil. Cuenta con una serie de opciones destinadas a resolver algunas de las necesidades más comunes en el desarrollo móvil además de una gran comunidad que aporta mejoras tanto al propio framework como una diversidad amplia de plugins.

MvvmCross

MvvmCross soporta una gran variedad de plataformas. En el caso de Xamarin.Forms se soporta:

  • PCL
  • Android
  • iOS
  • UWP

En el futuro, al pasar releases estables, se añadirán otras plataformas como Linux (Gtk) o Tizen.

Utilizar MvvmCross en Xamarin.Forms

MvvmCross cuenta con una serie de paquetes NuGet que nos facilitan muchísimo la integración con Xamarin.Forms. Hablamos de los paquetes StarterPack que se encargará de añadir tanto las librerías básicas necesarias como los archivos que nos permitirán utilizar MvvmCross en cada plataforma y de MvvmCross.Forms.

NOTA: A la hora de escribir este artículo, utilizamos la versión 5.4.2 de MvvmCross.

Tras añadir ambos paquetes a todos los proyectos de la solución, comenzamos a editar cada proyecto.

PCL

Comenzamos modificando la aplicación Xamarin.Forms. Para ello, cambiamos:

<FormsApplication xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    x:Class="HelloMvxForms.Core.App">

</FormsApplication>

Por:

<?xml version="1.0" encoding="utf-8" ?>
<mvx:MvxFormsApplication 
     xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     xmlns:mvx="clr-namespace:MvvmCross.Forms.Platform;assembly=MvvmCross.Forms"
     x:Class="HelloMvxForms.Core.App">
</mvx:MvxFormsApplication>

Igualmente, cambiamos App.xaml.cs:

public partial class App : FormsApplication

Por:

public partial class App : MvxFormsApplication

En esta misma clase, vamos a eliminarla línea encargada de estableder la MainPage. Vamos a indicar cual es la vista inicial de la aplicación, a continuación. Al añadir el paquete StarterPack en la PCL se añadió una clase llamada App que vamos a renombrar a MvxApp (puedes renombrarla a CoreApp, u otro nombre):

public class mvxApp : MvvmCross.Core.ViewModels.MvxApplication
{
     public override void Initialize()
     {
          CreatableTypes()
          .EndingWith("Service")
          .AsInterfaces()
          .RegisterAsLazySingleton();

          RegisterNavigationServiceAppStart<MvxMainViewModel>();
     }
}

Utilizamos el método RegisterNavigationServiceAppStart para indicar la viewmodel asociada a una vista que se mostrará como vista de inicio.

Todo listo a nivel de librería portable. Es hora de pasar a proyectos de cada plataforma.

Android

En cada plataforma vamos a contar con una clase llamada Setup encargada de hacer de bootstrapper ed MvvmCross (inicializa dependencias, componentes, etc). La mayoría de las acciones realizadas son virtuales, por lo que permiten personalización.

public class Setup : MvxFormsAndroidSetup
{
     public Setup(Context applicationContext) : base(applicationContext)
     {
     }

     protected override MvxFormsApplication CreateFormsApplication()
     {
          return new Core.App();
     }

     protected override IMvxApplication CreateApp()
     {
          return new Core.MvxApp();
     }

     protected override IMvxTrace CreateDebugTrace()
     {
          return new DebugTrace();
     }
}

Establecemos MvxFormsApplication y la implementación de IMvxApplication de nuestra PCL.

NOTA: Fíjate que el tipo base de la clase setup en Android es MvxFormsAndroidSetup. En la clase Setup añadida por StarterPack, el tipo es MvxAndroidSetup.

Por otro lado, modificamos la actividad principal para cambiar el tipo a MvxFormsAppCompatActivity:

public class MainActivity : MvxFormsAppCompatActivity
{
     protected override void OnCreate(Bundle bundle)
     {
          base.OnCreate(bundle);
          TabLayoutResource = Resource.Layout.Tabbar;
          ToolbarResource = Resource.Layout.Toolbar;
     }
}

iOS

Llegamos a iOS donde tendremos una serie de pasos a realizar, muy similares a Android. Comenzamos por la clase Setup:

public class Setup : MvxFormsIosSetup
{
     public Setup(IMvxApplicationDelegate applicationDelegate, UIWindow window)
     : base(applicationDelegate, window)
     {
     }

     protected override MvxFormsApplication CreateFormsApplication()
     {
          return new Core.App();
     }

     protected override IMvxApplication CreateApp()
     {
          return new Core.MvxApp();
     }

     protected override IMvxTrace CreateDebugTrace()
     {
          return new DebugTrace();
     }
}

También necesitamos modificar la clase AppDelegate:

[Register("AppDelegate")]
public partial class AppDelegate : MvxFormsApplicationDelegate
{
     public override UIWindow Window { get; set; }

     public override bool FinishedLaunching(UIApplication app, NSDictionary options)
     {
          Window = new UIWindow(UIScreen.MainScreen.Bounds);

          var setup = new Setup(this, Window);
          setup.Initialize();

          var startup = Mvx.Resolve<IMvxAppStart>();
          startup.Start();

          LoadApplication(setup.FormsApplication);

          Window.MakeKeyAndVisible();

          return true;
     }
}

Todo listo!

UWP

Al igual que en plataformas anteriores, comenzamos preparando la clase Setup:

public class Setup : MvxFormsWindowsSetup
{
     private readonly LaunchActivatedEventArgs launchActivatedEventArgs;

     public Setup(Windows.UI.Xaml.Controls.Frame rootFrame, LaunchActivatedEventArgs e) : base(rootFrame, e)
     {
          launchActivatedEventArgs = e;
     }

     protected override IMvxApplication CreateApp()
     {
          return new Core.MvxApp();
     }

     protected override IMvxWindowsViewPresenter CreateViewPresenter(IMvxWindowsFrame rootFrame)
     {
          Forms.Init(launchActivatedEventArgs);

          var presenter = new MvxFormsUwpViewPresenter(rootFrame, new MvxFormsApplication());
          Mvx.RegisterSingleton<IMvxViewPresenter>(presenter);

          return presenter;
     }

     protected override MvxLogProviderType GetDefaultLogProviderType() => MvxLogProviderType.None;
}

Modificamos la clase App.xaml.cs donde utilizaremos la clase Setup en el método OnLaunched tras inicializar Xamarin.Forms:

var setup = new Setup(rootFrame, e);
setup.Initialize();

Por último, en la página principal nativa del proyecto UWP:

public MainPage()
{
     InitializeComponent();

     var start = Mvx.Resolve<IMvxAppStart>();
     start.Start();

     var presenter = Mvx.Resolve<IMvxViewPresenter>() as MvxFormsUwpViewPresenter;

     LoadApplication(presenter.FormsApplication);
}

Llega la hora de trabajar con vistas y viewmodels

Las vistas, es decir, páginas en Xamarin.Forms deben extender de MvxContentPage. Por lo que debemos cambiar:

public partial class MvxMainView : ContentPage

Por:

public partial class MvxMainView : MvxContentPage<MvxMainViewModel>

De igual forma, en el XAML de la página, se debe utilizar el namespace:

xmlns:views="clr-namespace:MvvmCross.Forms.Core;assembly=MvvmCross.Forms"

Y definir la página como:

<views:MvxContentPage>

Por defecto, MvvmCross asociada vistas con ViewModels utilizando convención de nombres. Es decir, si tienes una vista llamada SignInView, la ViewModel debe llamarse SignInViewModel.

Cada ViewModel debe ser una clase que herede de MvxViewModel.

Ejemplo básico de uso de Mvx en Xamarin.Forms

Tienes un ejemplo de implementación básico disponible en GitHub:

Ver GitHub

Navegación

MvvmCross utiliza las ViewModels para realizar la navegación. Es decir, la navegación se realiza de ViewModel a Viewmodel en lugar de vista a vista. Tenemos disponible un servicio completo de navegación llamado IMvxNavigationService.

private readonly IMvxNavigationService _navigationService;

public FirstViewModel(IMvxNavigationService navigationService)
{
     _navigationService = navigationService;
}

Contamos con diferentes métodos para gestionar la navegación:

await _navigationService.Navigate<SecondViewModel, string>("parameter");

En el ejemplo anterior, navegamos a una vista llamada SecondView (asociada a la ViewModel llamada SecondViewModel) pasando un parámetro de tipo cadena.

También contamos con diferentes eventos que podemos utilizar en cada ViewModel para la gestión del ciclo de la navegación (navegar, recibir parámetros, etc.):

public override void Prepare(string parameter)
{
     Parameter = parameter;
}
Navegación

Tienes un ejemplo de implementación de navegación con MvvmCross disponible en GitHub:

Ver GitHub

NOTA: Para obtener más información acerca de la navegación en MvvmCross, consulta este enlace.

MvvmCross utiliza Presenter para navegar a vistas en cada plataforma nativa. Actúa como «pegamento» entre vistas y ViewModels. Como podemos deducir de su nombre, un Presenter toma una solicitud de presentación de una ViewModel y decide como presentar la UI. De esa forma, se logra una clara separación entre vistas y ViewModels. Esto también es clave para poder mostrar vistas nativas, Forms o una mezcla de ellas.

NOTA: Se puede cambiar tanto el comportamiento como del servicio de navegación como de Presenter.

Plugins

Haciendo una sencilla búsqueda de «plugin MvvmCross» en NuGet tendremos acceso a una gran variedad de paquetes. Tras añadir el paquete, podemos utilizar el plugin haciendo uso de inyección de dependencias o vía resolución de interfaz. Veamos un ejemplo haciendo uso de Acr.MvvmCross.Plugins.UserDialogs.

Mvx.RegisterSingleton<IUserDialogs>(() => UserDialogs.Instance);

A la hora de utilizar el plugin:

Mvx.Resolve<IUserDialogs>().Alert("Using Mvx Plugins!");
Uso de Plugins

Tienes un ejemplo de uso de plugins MvvmCross disponible en GitHub:

Ver GitHub

MvvmCross añade más funcionalidad en Xamarin.Forms como su propio sistema de enlace a datos (se puede utilizar perfectamente el de Xamarin.Forms) que añade opciones interesantes como Mutiple bindings o converters muy comunes por ejemplo. Sin embargo, en este artículo hemos visto los conceptos básicos necesarios en cualquier aplicación. En futuros artículos, continuaremos profundizando en otros conceptos así como ver otros frameworks y opciones!.

Más información

Deja un comentario

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