Construyendo Aplicaciones Multiplataforma con Xamarin: Perspectiva de un Desarrollador Android

Escribir código una vez y utilizarlo en múltiples plataformas ha sido un sueño para muchos desarrolladores software. Aunque esto ha sido posible desde hace un tiempo, siempre ponía en riesgo el mantenimiento, facilidad de prueba o peor, una mala experiencia de usuario.

Desarrollar aplicaciones móviles usando el nativo SDK es, probablemente, el punto de partida para muchos desarrolladores quienes tienen sus raíces en el reino de desarrollo de aplicaciones para escritorio. Programar idiomas sería una barrera para algunos: Si alguien tuviera experiencia en el desarrollo de escritorio Java o aplicaciones back-end, pasar a Android podría ser más fácil que comenzar con Objective-C desde cero para iOS.

Siempre tuve mis dudas con el desarrollo de aplicaciones multiplataforma. Los frameworks basados en JavaScript como Sencha, Cordova, Titanium, etc. nunca han sido buenas opciones cuando el desempeño es importante. La falta de API y una experiencia de usuario particular era algo clave con estos frameworks.

Pero, descubrí Xamarin.

En este artículo, aprenderás cómo puedes usar Xamarin para compartir código en múltiples plataformas sin perjudicar ningún otro aspecto del desarrollo de aplicaciones móviles. El artículo se enfocará, en particular, en Android y iOS pero puedes usar cualquier otra de las plataformas que apoya Xamarin.

¿Qué es Xamarin?

Xamarin es una plataforma de desarrollo que te permite escribir aplicaciones multiplataforma—aunque nativa—para iOS, Android y Windows Phone en C# y .NET.

Xamarin proporciona bindings C# a las API nativas Android y iOS. Esto te da el poder para usar toda la interfaz de usuario nativo, notificaciones, gráficos, animación y otras características de teléfono— y todas usan C#.

Xamarin alcanza cada lanzamiento nuevo de Android y iOS, con un lanzamiento que incluye bindings para sus nuevas API.

El puerto de .NET de Xamarin incluye características como tipos de data, genéricos, colección de papelera de reciclaje, language-integrated query (LINQ), patrón de programación asincrónica, delegación y un subconjunto de Windows Communication Foundation (WCF). Las bibliotecas son manejadas con un linger para incluir sólo los componentes referidos.

Xamarin.Forms es una capa que se encuentra sobre las otros bindings UI y la API Windows Phone, la cual proporciona una biblioteca de interfaz de usuario completamente multiplataforma.

Escribir Aplicaciones Multiplataforma

Para poder escribir aplicaciones multiplataforma con Xamarin, los desarrolladores necesitan escoger uno de los dos tipos de proyecto disponibles:

  • Portable Class Library (PCL)
  • Proyecto Compartido

El PCL te permite escribir código que se puede compartir entre plataformas múltiples pero con una limitante. Dado que no todas las API .NET están disponibles en todas las plataformas, con un proyecto PLC, lo estarás limitando a ser ejecutado solo en plataformas para las que está dirigido.

Durante el proceso de construcción, un PCL se compila en distintos DLL y se carga por Mono durante la ejecución. Una implementación diferente pero de la misma interfaz se puede proporcionar durante la ejecución.

Por otra parte, los proyectos compartidos te dan más control, porque te permiten escribir código de plataforma específica para cada plataforma que quieras apoyar. El código en un proyecto compartido puede contener directivas de conversión de datos que activarán o desactivarán secciones de código, dependiendo de qué proyecto de aplicación está usando el código.

A diferencia de un PCL, un proyecto compartido no produce ningún DLL. El código está incluido directamente en el proyecto final.

Dar Estructura a tu Código Multiplataforma con MvvmCross

Un código reusable puede ayudar a ahorrar dinero y tiempo a los equipos de desarrollo. Sin embargo, un código bien estructurado le hace la vida más fácil a los desarrolladores. Nadie aprecia un código bien escrito y libre de bugs como los desarrolladores.

Xamarin solo proporciona un mecanismo el cual hace más fácil escribir código reusable multiplataforma.

Los desarrolladores móviles están familiarizados con escenarios donde tienen que escribir la misma lógica dos veces o más para poder apoyar iOS, Android y otras plataformas. Pero con Xamarin, como lo expliqué en el capítulo pasado, es fácil reusar un código en otras plataformas, aunque haya sido escrito para una plataforma en particular.

Entonces, ¿en qué momento entra MvvmCross?

MvvmCross, como su nombre lo insinúa, hace posible el uso del patrón MVVM en aplicaciones Xamarin. Viene con varias bibliotecas, APIs y servicios que son muy importantes en el desarrollo de la aplicación multiplataforma.

MvvmCross puede reducir, significativamente, la cantidad de código boilerplate qué habrías escrito (en ocasiones repetidas veces en distintos idiomas) en cualquier otro acercamiento al desarrollo de aplicaciones.

Estructura de una Solución MvvmCross

La comunidad MvvmCross recomienda una manera muy simple y eficiente para estructurar una solución MvvmCross:

<ProjectName>.Core
<ProjectName>.UI.Droid
<ProjectName>.UI.iOS

El proyecto Core en una solución MvvmCross está relacionada al código reusable. El proyecto Core es un proyecto PCL de Xamarin, en el que el enfoque principal es la reusabilidad.

Cualquier código escrito en Core debería ser, en lo posible, agnóstico de plataforma. Sólo debería contener lógica, la cual puede ser reusada en todas las plataformas. El proyecto Core no debe usar las API Android ni iOS, tampoco ingresar nada específico a ninguna plataforma.

La capa lógica de negocio, capa de data y la comunicación back-end, son perfectos candidatos para ser incluidos en el proyecto Core. La navegación a través de vista de jerarquía (actividades, fragmentos, etc.) se conseguirá en el Core.

Antes de continuar, es necesario entender un patrón de diseño arquitectural, lo cual es crucial para entender más sobre MvvmCross y cómo funciona. Como se puede percibir por su nombre, MvvmCross depende, grandemente, del patrón MVVM.

MVVM es un patrón de diseño arquitectural, el cual facilita la separación de la interfaz gráfica de usuario de la lógica de negocio y la data back-end.

¿Cómo se usa este patrón en MvvmCross?

Bueno, dado que queremos alcanzar un alto nivel de reusabilidad de nuestro código, queremos tener lo más posible en nuestro Core, que es un proyecto PLC. Ya que las vistas son la única parte del código que difiere de una plataforma a otra, no las podemos reusar entre plataformas. Esa parte se implementa en los proyectos relacionados a la plataforma.

MvvmCross nos da la habilidad de crear una navegación de aplicación desde Core usando ViewModels.

Ya con lo básico y los detalles técnicos encaminados, empecemos con Xamarin creando nuestro propio proyecto Core MvvmCross:

Crear un Proyecto Core MvvmCross

Abre Xamarin Studio y crea una solución llamada ToptalExampleSolution:

Dado que estamos creando un proyecto Core, es buena idea mantener la convención de dar nombres. Asegúrate de que el sufijo Core sea agregado al nombre del proyecto.

Para poder obtener el apoyo de MvvmCross, se requiere agregar bibliotecas MvvmCross a nuestro proyecto. Para agregar esto podemos usar apoyo incluido para NuGet en Xamarin Studio.

Para agregar una biblioteca, da clic derecho en la carpeta Packages y selecciona la opción Add Packages… .

En el campo de búsqueda, podemos buscar MvvmCross, el cual actuará como filtro y encontrará resultados relacionados a MvvmCross como se muestra abajo:

Al dar clic en el botón Add Package se agregará al proyecto.

Con MvvmCross agregado al proyecto, estamos listos para escribir nuestro código Core.

Vamos a definir nuestro primer ViewModel. Para crear uno, crea una jerarquía de carpetas de la siguiente manera:

Aquí ves de que se trata cada carpeta:

  • Modelos: Modelos de dominio que representan el contenido de bienes raíces
  • Servicios: Una carpeta que lleva nuestro servicio (lógica de negocio, base de datos, etc.)
  • ViewModel: La forma como nos comunicamos con nuestros modelos

Nuestro primer ViewModel se llama FirstViewModel.cs

   public class FirstViewModel : MvxViewModel
   {
       private string _firstName;
       private string _lastName;
       private string _fullName;
 
       public string FirstName
       {
           get
           {
               return _firstName;
           }
           set
           {
               _lastName = value;
               RaisePropertyChanged();
           }
       }
 
       public string LastName
       {
           get
           {
               return _lastName;
           }
 
           set
           {
               _lastName = value;
               RaisePropertyChanged();
           }
       }
 
       public string FullName
       {
           get
           {
               return _fullName;
           }
           set
           {
               _fullName = value;
               RaisePropertyChanged();
           }
       }
 
       public IMvxCommand ConcatNameCommand
       {
           get
           {
               return new MvxCommand(() =>
               {
                   FullName = $"{FirstName} {LastName}";
               });
           }
 
         public IMvxCommand NavigateToSecondViewModelCommand
       {
           get
           {
               return new MvxCommand(() =>
               {
                   ShowViewModel<SecondViewModel>();
               });
           }
       }
   }

Ahora que tenemos nuestro primer ViewModel, podemos crear nuestra primera vista y unir todo.

UI de Android

Para mostrar el contenido del ViewModel, debemos crear un UI.

El primer paso para crear un UI de Android es crear un proyecto Android en la solución actual. Para hacer esto, da clic derecho en el nombre de la solución y selecciona Add -> Add New Project…. En el wizard, selecciona aplicación Android y asegúrate de nombrar tu proyecto ToptalExample.UI.Droid.

Como lo describí antes, ahora necesitamos agregar dependencias MvvmCross para Android. Para hacer eso, sigue los mismos pasos del proyecto Core para agregar dependencias NuGet.

Después de agregar las dependencias MvvmCross, es necesario agregar una referencia para nuestro proyecto Core para así poder usar nuestro código allí escrito. Para agregar una referencia para el proyecto PCL, da clic derecho en la carpeta Referencias y selecciona la opción Edit References… . En la tabulación Proyectos, selecciona el proyecto Core creado previamente y da clic en OK.

La siguiente parte puede ser un poco difícil de entender.

Ahora debemos decirle a MvvmCross como instalar nuestra aplicación. Para hacer esto debemos crear la clase Setup:

namespace ToptalExample.UI.Droid
{
   public class Setup : MvxAndroidSetup
   {
       public Setup(Context context)
           : base(context)
       {
       }
 
       protected override IMvxApplication CreateApp()
       {
           return new Core.App();
       }
   }
}

Como se puede ver en la clase, le estamos diciendo a MvvmCross que haga una base CreateApp en la implementación Core.App, la cual es una clase definida en Core y se muestra abajo:

   public class App : MvxApplication
   {
       public override void Initialize()
       {
           RegisterAppStart(new AppStart());
       }
   }
 
   public class AppStart : MvxNavigatingObject, IMvxAppStart
   {
       public void Start(object hint = null)
       {
           ShowViewModel<FirstViewModel>();
       }
   }

En la clase App, estamos creando una instancia de AppStart, que va a mostrar nuestro primer ViewModel.

Lo único que queda por hacer ahora es crear un archivo de diseño Android, el cual será unido por MvvmCross:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:local="http://schemas.android.com/apk/res-auto"
   android:orientation="vertical"
   android:layout_width="match_parent"
   android:layout_height="match_parent">
   <EditText
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       local:MvxBind="Text FirstName" />
   <EditText
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       local:MvxBind="Text LastName" />
   <TextView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       local:MvxBind="Text FullName" />
   <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       local:MvxBind="Click ConcatNameCommand" />
  <Button
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       local:MvxBind="Click NavigateToSecondViewModelCommand" />
</LinearLayout>

En el archivo de diseño, tenemos bindings las cuales se resuelven, automáticamente, por MvvmCross. Para EditText, vamos a crear una binding para la propiedad Texto, la cual será una binding de doble vía. Cualquier cambio invocado desde el lado de ViewModel será reflejado automáticamente en la vista y viceversa.

La clase View puede ser una actividad o un fragmento. Para simplificar, estamos usando una actividad que carga el diseño dado:

[Activity(Label = "ToptalExample.UI.Droid", Theme = "@style/Theme.AppCompat", MainLauncher = true, Icon = "@mipmap/icon")]
   public class MainActivity : MvxAppCompatActivity<FirstViewModel>
   {
       protected override void OnCreate(Bundle bundle)
       {
           base.OnCreate(bundle);
           SetContentView(Resource.Layout.Main);
 
       }
   }

Para el primer botón, tenemos un comando binding, lo cual significa que cuando des clic en el botón MvvmCross, éste invocará ContactNameCommand desde el ViewModel.

Para el segundo botón, vamos a mostrar otro ViewModel.

IU de iOS

Crear un proyecto iOS no es muy distinto a crear un proyecto Android. Debes seguir pasos similares para agregar un nuevo proyecto, solo que en esta oportunidad, en vez de Android, solo crea un proyecto iOS. Solo asegúrate de mantener la convención de dar nombre activa.

Después de agregar el proyecto iOS, debes agregar dependencias para MvvmCross iOS. Los pasos son exactamente los mismos que para Core y Android (da clic derecho en Referencias en tu proyecto iOS y da a Add References…).

Ahora, como hicimos con Android, es necesario crear una clase Setup, la cual le dirá a MvvmCross como instalar nuestra aplicación.

public class Setup : MvxIosSetup
   {
       public Setup(MvxApplicationDelegate appDelegate, IMvxIosViewPresenter presenter)
           : base(appDelegate, presenter)
       {
       }
 
       protected override MvvmCross.Core.ViewModels.IMvxApplication CreateApp()
       {
           return new App();
       }
   }

Nota que la clase Setup ahora extiende MvxIosSetup y, para Android, extendía MvxAndroidSetup.

Un agregado aquí es que tenemos que cambiar nuestra clase AppDelegate .

AppDelegate en iOS es responsable por lanzar la interfaz de usuario, así que tenemos que ver como las vistas van a ser presentadas en iOS. Puedes aprender más sobre los presentadores aquí.

[Register("AppDelegate")]
   public class AppDelegate : MvxApplicationDelegate
   {
       // class-level declarations
 
       public override UIWindow Window
       {
           get;
           set;
       }
 
       public override bool FinishedLaunching(UIApplication application, NSDictionary launchOptions)
       {
           Window = new UIWindow(UIScreen.MainScreen.Bounds);
 
           var presenter = new MvxIosViewPresenter(this, Window);
 
           var setup = new Setup(this, presenter);
           setup.Initialize();
 
           var startup = Mvx.Resolve<IMvxAppStart>();
           startup.Start();
 
           Window.MakeKeyAndVisible();
 
           return true;
       }
}

Para presentar nuestro VIewModel, tenemos que crear una vista. Para ese caso en particular, vamos a crear un ViewController, da clic derecho en el proyecto y selecciona Add -> New File y selecciona ViewController en la sección iOS, a los cuales le vamos a dar el nombre de FirstViewController.

Xamarin crea tres archivos, en los que vamos a definir como nuestros bindings. A diferencia de Android, para iOS, tenemos que definir nuestros bindings de una manera diferente, a través de código ( aunque podemos hacer eso en Android también y, para algunos casos, es requerido hacerlo).

Cuando es requerido navegar entre vistas, se hace vía el ViewModel. En el comando NavigateToSecondViewModelCommand, el método ShowViewModel<SecondViewModel>() encontrará la vista apropiada y la navegará.

Pero, ¿cómo sabe MVVMCross que vista cargar?

No hay nada mágico en eso. Cuando creamos una vista para Android (Actividad o Fragmento) estamos extendiendo una de las clases base con parámetros de tipo (MvxAppCompatActivity<VM>). Cuando llamamos a ShowViewMolel<VM>, MvvmCross busca una View(Vista) lo cual extiende la clase Activity o Fragment con parámetros de tipo VM. Por esto no puedes tener dos clases de vista para el mismo ViewModel.

Inversión de Control

Xamarin está solo proporcionando envolturas C# alrededor de las API nativas, no proporcionando forma alguna de inyección de dependencia (DI) o mecanismo de inversión de control (loC).

Sin una inyección de dependencias de tiempo de ejecución o inyección de tiempo compilado, no es fácil crear componentes vagamente unidos, reusables, para hacer pruebas y fáciles de mantener. La idea de loC y DI se conoce desde hace mucho tiempo; los detalles sobre loC se pueden encontrar en muchos artículos en línea. Puedes aprender más sobre estos patrones del artículo introductorio de Martin Fowler.

IoC ha estado disponible desde las primeras versiones de MvvmCrosses y permite inyección de dependencias en tiempos de ejecución, cuando la aplicación inicia y cuando sea que se requiera.

Para obtener componentes débilmente acoplados, nunca deberíamos requerir implementaciones de clases concretas. Requerir implementaciones concretas limita la habilidad de cambiar el comportamiento de las implementaciones durante el tiempo de ejecución (no la puedes reemplazar con otra implementación). Esto dificulta hacer pruebas en estos componentes.

Por esta razón, vamos a declarar una interfaz para la que vamos a tener una implementación concreta.

public interface IPasswordGeneratorService
{
    string Generate(int length);
}

E implementación:

   public class PasswordGeneratorService : IPasswordGeneratorService
   {
       public string Generate(int length)
       {
           var valid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
           var res = new StringBuilder();
           var rnd = new Random();
           while (0 < length--)
           {
               res.Append(valid[rnd.Next(valid.Length)]);
           }
           return res.ToString();
       }
   }

Ahora, nuestro ViewModel puede requerir una instancia de la interfaz IPasswordGenerationService, la cual es nuestra responsabilidad proporcionar.

Para que MvvmCross inyecte la implementación PasswordGeneratorService al momento de ejecución, necesitamos decirle a MvvmCross que implementación usar. Si queremos usar una implementación para ambas plataformas, podemos registrar la implementación en App.cs, después del registro de la aplicación:

public override void Initialize()
       {
           RegisterAppStart(new AppStart());
 
           Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, PasswordGeneratorService>();
       }

El llamado de arriba al método estático LazyConstructAndRegisterSingleton<TInterface, TType> registra la implementación a ser inyectada. Este método registra la implantación apropiada pero no crea un objeto.

El objeto es creado solo cuando es requerido y solo una vez, ya que está registrado como un solitario.

Si queremos crear un objeto solitario enseguida, se puede hacer al llamar Mvx.RegisterSingleton<TInterface>().

Hay caso en los que no queremos tener solo solitarios en nuestra aplicación. Puede que nuestro objeto no sea a prueba de fallos, o tal vez haya otra razón por la que queremos siempre una nueva instancia. Si ese es el caso, MvvmCross proporciona el método Mvx.RegisterType<TInterface,TType>(), el cual se puede usar para registrar la implementación en una manera en la que ejemplifica una nueva instancia donde sea requerida.

En caso de que necesites proporcionar implementaciones concretas para cada plataforma, lo puedes hacer en los proyectos de plataforma específica:

public class DroidPasswodGeneratorService : IPasswordGeneratorService
   {
       public string Generate(int length)
       {
           return "DroidPasswordGenerator";
       }
   }

Y el registro de nuestra implementación se hace en la clase Setup.cs bajo el proyecto Droid:

protected override void InitializePlatformServices()
       {
           base.InitializePlatformServices();
 
           Mvx.LazyConstructAndRegisterSingleton<IPasswordGeneratorService, DroidPasswodGeneratorService>();
 
       }

Después de iniciar el código PCL, MvvmCross llamará a InitializePlatformServices y registrará nuestras implementaciones de servicio específico de plataforma.

Cuando registramos implementaciones solitarias múltiples, MvvmCross solo usará la última implementación registrada. Todos los otros registros serán rechazados.

Construir Aplicaciones Multiplataforma con Xamarin

En este artículo, hemos visto como Xamarin te permite compartir código entre diferentes plataformas y mantener el sentimiento nativo y el desempeño de las aplicaciones.

MvvmCross otorga otra capa de abstracción, ampliando aún más la experiencia de construir aplicaciones multiplataforma con Xamarin. El patrón MVVM proporciona una manera de crear navegación y flujos de interacción de usuario, que son comunes para todas las plataformas, haciendo así la cantidad de código de plataforma específica que necesitas escribir limitado solo a vistas.

Espero que este artículo te haya dado una razón para echar un vistazo a Xamarin, y también que te haya motivado a construir con ella tu próxima aplicación multiplataforma.

Artículo vía TopTal 

FV

FV

Diseñador gráfico y web, con ganas de trabajar y aprender todo lo posible de este campo tan variado. Creativo tanto en la vida laboral como personal. Diseñar es el arte de transmitir gráficamente lo que uno imagina. Imagina, crea, diseña.

Deja un comentario