Creación de aplicaciones multiplataforma con Xamarin: perspectiva de un desarrollador de Android

Publicado: 2022-03-11

Escribir código una vez y usarlo en múltiples plataformas ha sido el sueño de muchos desarrolladores de software. Aunque esto ha sido posible desde hace algún tiempo, siempre tuvo el costo de la mantenibilidad, la facilidad de prueba o, lo que es peor, la mala experiencia del usuario.

El desarrollo de aplicaciones móviles con el SDK nativo es probablemente el punto de partida para todos los desarrolladores que tienen sus raíces en el ámbito del desarrollo de aplicaciones de escritorio. Los lenguajes de programación se convertirían en una barrera para algunos: si alguien tuviera experiencia en el desarrollo de aplicaciones Java de escritorio o back-end, cambiarse a una empresa de desarrollo de aplicaciones móviles y trabajar con Android sería mucho más fácil que comenzar con Objective-C desde cero para iOS.

Siempre fui escéptico sobre el desarrollo de aplicaciones multiplataforma. Los marcos basados ​​en JavaScript como Sencha, Cordova, Titanium, etc. nunca demuestran ser una buena elección cuando el rendimiento es importante. La falta de API y la experiencia de usuario peculiar eran un hecho con estos marcos.

Pero entonces, me encontré con Xamarin.

Desarrollo multiplataforma con Xamarin

En este artículo, aprenderá cómo puede usar Xamarin para compartir código en múltiples plataformas sin comprometer ninguno de los otros aspectos del desarrollo de aplicaciones móviles. El artículo se centrará en Android e iOS en particular, pero puede usar un enfoque similar para agregar compatibilidad con cualquier otra plataforma compatible con Xamarin.

¿Qué es Xamarin?

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

Xamarin proporciona enlaces de C# a las API nativas de Android e iOS. Esto le brinda el poder de usar toda la interfaz de usuario nativa, notificaciones, gráficos, animaciones y otras funciones del teléfono de Android e iOS, todo usando C#.

Cada nueva versión de Android e iOS se combina con Xamarin, con una nueva versión que incluye enlaces para sus nuevas API.

El puerto de .NET de Xamarin incluye características como tipos de datos, genéricos, recolección de elementos no utilizados, consulta integrada en el lenguaje (LINQ), patrones de programación asíncrona, delegados y un subconjunto de Windows Communication Foundation (WCF). Las bibliotecas se administran con un retraso para incluir solo los componentes a los que se hace referencia.

Xamarin.Forms es una capa sobre los otros enlaces de la interfaz de usuario y la API de Windows Phone, que proporciona una biblioteca de interfaz de usuario completamente multiplataforma.

El alcance de Xamarin

Escribir aplicaciones multiplataforma

Para escribir aplicaciones multiplataforma con Xamarin, los desarrolladores deben elegir uno de los dos tipos de proyectos disponibles:

  • Biblioteca de clases portátil (PCL)
  • Proyecto compartido

PCL le permite escribir código que se puede compartir entre múltiples plataformas, pero con una limitación. Dado que no todas las API de .NET están disponibles en todas las plataformas, con un proyecto PCL, lo limitará para que se ejecute en las plataformas a las que está destinado.

Conexiones y limitaciones de Xamarin

La siguiente tabla muestra qué API están disponibles en qué plataformas:

Rasgo .NET Framework Aplicaciones de la Tienda Windows Luz plateada Telefono windows Xamarin
Centro Y Y Y Y Y
LINQ Y Y Y Y Y
IQueryable Y Y Y 7.5+ Y
Publicación por entregas Y Y Y Y Y
Anotaciones de datos 4.0.3+ Y Y Y Y

Durante el proceso de compilación, se compila una PCL en archivos DLL separados y Mono la carga durante el tiempo de ejecución. Se puede proporcionar una implementación diferente de la misma interfaz durante el tiempo de ejecución.

Por otro lado, los proyectos compartidos le brindan más control al permitirle escribir código específico de plataforma para cada plataforma que desee admitir. El código en un proyecto compartido puede contener directivas de compilación que habilitarán o deshabilitarán secciones de código según el proyecto de aplicación que esté usando el código.

A diferencia de una PCL, un proyecto compartido no produce ninguna DLL. El código se incluye directamente en el proyecto final.

Dando estructura a su código multiplataforma con MvvmCross

El código reutilizable puede ahorrar dinero y tiempo a los equipos de desarrollo. Sin embargo, un código bien estructurado facilita mucho la vida de los desarrolladores. Nadie aprecia más el código libre de errores bien escrito que los desarrolladores.

Xamarin por sí mismo proporciona un mecanismo que facilita mucho la escritura de código multiplataforma reutilizable.

Los desarrolladores móviles están familiarizados con escenarios en los que tienen que escribir la misma lógica dos veces o más para admitir iOS, Android y otras plataformas. Pero con Xamarin, como se explicó en el capítulo anterior, es fácil reutilizar el código que está escrito para una plataforma también para otras plataformas.

Entonces, ¿dónde entra MvvmCross?

MvvmCross, como su nombre puede haber insinuado, hace posible usar el patrón MVVM en las aplicaciones de Xamarin. Viene con un montón de bibliotecas, API y utilidades que son realmente útiles en el desarrollo de aplicaciones multiplataforma.

MvvmCross puede reducir significativamente la cantidad de código repetitivo que habría escrito (a veces varias veces en diferentes idiomas) en cualquier otro enfoque de desarrollo de aplicaciones.

Estructura de una solución MvvmCross

La comunidad MvvmCross recomienda una forma bastante simple y eficiente de estructurar una solución MvvmCross:

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

El proyecto Core en una solución MvvmCross está relacionado con el código reutilizable. El proyecto Core es un proyecto PCL de Xamarin donde el enfoque principal es la reutilización.

Cualquier código escrito en Core debe ser independiente de la plataforma en la mayor medida posible. Solo debe contener lógica que pueda reutilizarse en todas las plataformas. El proyecto Core no debe usar ninguna API de Android o iOS ni acceder a nada específico de ninguna plataforma.

La capa de lógica empresarial, la capa de datos y la comunicación de back-end son candidatas perfectas para incluirlas en el proyecto Core. La navegación a través de la jerarquía de vistas (actividades, fragmentos, etc.) se logrará en el Core.

Antes de continuar, es necesario comprender un patrón de diseño arquitectónico que es crucial para comprender MvvmCross y cómo funciona. Como puede verse por el nombre, MvvmCross depende en gran medida del patrón MVVM.

MVVM es un patrón de diseño arquitectónico que facilita la separación de la interfaz gráfica de usuario de la lógica comercial y los datos de back-end.

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

Bueno, dado que queremos lograr una alta reutilización de nuestro código, queremos tener todo lo que podamos en nuestro Core, que es un proyecto PCL. Dado que las vistas son la única parte del código que difiere de una plataforma a otra, no podemos reutilizarlas entre plataformas. Esa parte se implementa en los proyectos relacionados con la plataforma.

MvvmEstructura cruzada

MvvmCross nos brinda la capacidad de orquestar la navegación de aplicaciones desde el Core usando ViewModels.

Con los conceptos básicos y los detalles técnicos fuera del camino, comencemos con Xamarin creando nuestro propio proyecto MvvmCross Core:

Creación de un proyecto MvvmCross Core

Abra Xamarin Studio y cree una solución llamada ToptalExampleSolution :

Creando la solución

Dado que estamos creando un proyecto principal, es una buena idea ceñirse a la convención de nomenclatura. Asegúrese de agregar el sufijo Core al nombre del proyecto.

Para obtener soporte para MvvmCross, es necesario agregar bibliotecas MvvmCross a nuestro proyecto. Para agregar eso, podemos usar el soporte integrado para NuGet en Xamarin Studio.

Para agregar una biblioteca, haga clic con el botón derecho en la carpeta Paquetes y seleccione la opción Agregar paquetes… .

En el campo de búsqueda, podemos buscar MvvmCross, que filtrará los resultados relacionados con MvvmCross como se muestra a continuación:

Filtrado de resultados

Al hacer clic en el botón Agregar paquete , se agregará al proyecto.

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

Definamos nuestro primer ViewModel. Para crear uno, cree una jerarquía de carpetas de la siguiente manera:

Jerarquía de carpetas recomendada

Esto es de lo que trata cada una de las carpetas:

  • Modelos: modelos de dominio que representan contenido de estado real
  • Servicios: una carpeta que contiene nuestro servicio (lógica de negocios, base de datos, etc.)
  • ViewModel: la forma en que 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 las cosas.

Interfaz de usuario de Android

Para mostrar el contenido de ViewModel, necesitamos crear una interfaz de usuario.

El primer paso para crear una interfaz de usuario de Android es crear un proyecto de Android en la solución actual. Para ello, haga clic con el botón derecho en el nombre de la solución y seleccione Agregar -> Agregar nuevo proyecto… . En el asistente, seleccione la aplicación de Android y asegúrese de nombrar su proyecto ToptalExample.UI.Droid .

Como se describió anteriormente, ahora debemos agregar las dependencias de MvvmCross para Android. Para hacerlo, siga los mismos pasos que para el proyecto Core para agregar dependencias de NuGet.

Después de agregar las dependencias de MvvmCross, es necesario agregar una referencia a nuestro proyecto Core para que podamos usar nuestro código escrito allí. Para agregar una referencia al proyecto PCL, haga clic con el botón derecho en la carpeta Referencias y seleccione la opción Editar referencias… . En la pestaña Proyectos, seleccione el proyecto principal creado anteriormente y haga clic en Aceptar.

Adición de una referencia al proyecto PCL

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

Ahora tenemos que decirle a MvvmCross cómo debe configurar nuestra aplicación. Para hacer eso, tenemos que crear una clase de Setup :

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

Como puede verse en la clase, le estamos diciendo a MvvmCross que CreateApp en función de la implementación de Core.App , que es una clase definida en Core y que se muestra a continuación:

 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 mostrará nuestro primer ViewModel.

Lo único que queda ahora es crear un archivo de diseño de Android que estará vinculado por MvvmCross:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro 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 enlaces que MvvmCross resuelve automáticamente. Para EditText , estamos creando un enlace para la propiedad Text, que será un enlace bidireccional. Cualquier cambio invocado desde el lado de ViewModel se reflejará 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 enlace de comando, lo que significa que cuando hacemos clic en el botón, MvvmCross invocará ContactNameCommand desde ViewModel.

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

Interfaz de usuario de iOS

Crear un proyecto de iOS no es realmente diferente de crear un proyecto de Android. Debe seguir pasos similares para agregar un nuevo proyecto, solo que esta vez, en lugar de Android, simplemente cree un proyecto de iOS. Solo asegúrese de mantener la convención de nomenclatura consistente.

Después de agregar el proyecto iOS, debe agregar dependencias para MvvmCross iOS. Los pasos son absolutamente los mismos que para Core y Android (haga clic con el botón derecho en Referencias en su proyecto de iOS y haga clic en Agregar referencias... ).

Ahora, como hicimos con Android, se requiere crear una clase de Setup , que le indicará a MvvmCross cómo configurar 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(); } }

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

Una adición aquí es que tenemos que cambiar nuestra clase AppDelegate .

AppDelegate en iOS es responsable de iniciar la interfaz de usuario, por lo que debemos indicar cómo se presentarán las vistas en iOS. Puede obtener más información 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, necesitamos crear una vista. Para ese caso, vamos a crear un ViewController haciendo clic con el botón derecho en el proyecto y seleccionando Agregar -> Nuevo archivo y seleccionando ViewController en la sección de iOS, que vamos a llamar FirstViewController.

Xamarin crea tres archivos en los que vamos a definir cuáles serán nuestros enlaces. A diferencia de Android, para iOS, tenemos que definir nuestros enlaces de una manera diferente, a través del código (aunque también podemos hacerlo en Android y, en algunos casos, es obligatorio hacerlo).

Cuando se requiere navegar entre vistas, se hace a través de ViewModel. En el comando NavigateToSecondViewModelCommand , el método ShowViewModel<SecondViewModel>() encontrará la vista adecuada y navegará hasta ella.

Pero, ¿cómo sabe MVVMCross qué vista cargar?

No hay ninguna magia 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 que amplíe la clase Activity o Fragment con parámetros de tipo VM . Esta es la razón por la que no se le permite tener dos clases de vista para el mismo modelo de vista.

Inversión de control

Dado que Xamarin simplemente proporciona contenedores de C# alrededor de las API nativas, no proporciona ninguna forma de inyección de dependencia (DI) o mecanismo de inversión de control (IoC).

Sin inyección de tiempo de ejecución de dependencias o inyección de tiempo de compilación, no es fácil crear componentes poco acoplados, reutilizables, comprobables y fáciles de mantener. La idea de IoC y DI se conoce desde hace mucho tiempo; Los detalles sobre IoC se pueden encontrar en muchos artículos en línea. Puede obtener más información sobre estos patrones en el artículo introductorio de Martin Fowler.

IoC ha estado disponible desde las primeras versiones de MvvmCrosses y permite la inyección de dependencias en tiempo de ejecución cuando se inicia la aplicación y siempre que se requieran.

Para obtener componentes débilmente acoplados, nunca deberíamos requerir implementaciones concretas de clases. Requerir implementaciones concretas limita la capacidad de cambiar el comportamiento de las implementaciones durante el tiempo de ejecución (no puede reemplazarlo con otra implementación). Hace que sea difícil probar estos componentes.

Por eso, vamos a declarar una interfaz para la cual 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 val; var res = new StringBuilder(); var rnd = new Random(); while (0 < length--) { res.Append(valid[rnd.Next(valid.Length)]); } return res.ToString(); } }

Nuestro ViewModel ahora puede requerir una instancia de la interfaz IPasswordGenerationService , que somos responsables de proporcionar.

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

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

La llamada anterior al método estático LazyConstructAndRegisterSingleton<TInterface, TType> registra la implementación que se inyectará. Este método registra la implementación apropiada pero no crea un objeto.

El objeto se crea solo cuando se requiere y solo una vez ya que se registra como singleton.

Si queremos crear un objeto singleton de inmediato, se puede lograr llamando a Mvx.RegisterSingleton<TInterface>() .

Hay casos en los que no queremos tener solo singletons en nuestra aplicación. Nuestro objeto puede no ser seguro para subprocesos o puede haber alguna otra razón por la que queremos tener siempre una nueva instancia. Si ese es el caso, MvvmCross proporciona el método Mvx.RegisterType<TInterface,TType>() , que se puede usar para registrar la implementación de una manera que instancia una nueva instancia siempre que sea necesario.

En caso de que necesite proporcionar implementaciones concretas separadas para cada plataforma, siempre puede hacerlo en los proyectos específicos de la plataforma:

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

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

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

Después de la inicialización del código PCL, MvvmCross llamará a InitializePlatformServices y registrará las implementaciones de servicios específicas de nuestra plataforma.

Cuando registramos múltiples implementaciones de singleton, MvvmCross usará solo la implementación que se registró en último lugar. Todos los demás registros serán descartados.

Cree aplicaciones multiplataforma con Xamarin

En este artículo, ha visto cómo Xamarin le permite compartir código entre diferentes plataformas y aún así mantener la sensación y el rendimiento nativos de las aplicaciones.

MvvmCross brinda otra capa de abstracción que mejora aún más la experiencia de crear aplicaciones multiplataforma con Xamarin. El patrón MVVM proporciona una forma de crear flujos de navegación e interacción del usuario que son comunes para todas las plataformas, lo que hace que la cantidad de código específico de la plataforma que necesita para escribir se limite solo a las vistas.

Espero que este artículo le haya dado una razón para echar un vistazo a Xamarin y lo haya motivado a crear su próxima aplicación multiplataforma con él.