Creando la interfaz de Netflix con Xamarin.Forms

Introducción

La evolución de Xamarin.Forms es meritoria. En los últimos tiempos se han recibido novedades interesantes como efectos, vistas nativas, Forms Embedding, etc. Sin embargo, en muchos casos se sigue asociado a desarrollos muy simples o formularios básicos.

Realmente, en el estado actual de Xamarin.Forms se pueden conseguir aplicaciones nativas de gran escala, con interfaces cuidadas y con alta integración con la plataforma. Hay que tener en cuenta el conjunto de Custom Renderers (código específico en cada plataforma) necesario para lograrlo.

NOTA: La elección entre Xamarin Classic o Xamarin.Forms es importante. Es necesario evaluar la aplicación a desarrollar, el conjunto de características específicas de cada plataforma (que pueden requerir un Custom Renderer), etc. 

En este artículo, vamos a tomar como referencia una aplicación bastante conocida, disponible en las listas de destacados de las diferentes tiendas, Netflix. Vamos a desarrollar la interfaz de la aplicación móvil de Netflix con Xamarin.Forms paso a paso.

¿Cuántos Custom Renderers crees que serán necesarios?, ¿qué cantidad de código podremos compartir?, ¿tendremos buen resultado final?.

La pantalla de selección de perfil

Vamos a comenzar partiendo de una pantalla bastante representativa y común en Netflix, la selección de usuario o perfil.

Vamos a analizar características de la pantalla y a determinar que vamos a necesitar:

  • Se puede ver una barra de navegación. Algo que podemos conseguir de forma sencilla en Xamarin.Forms gracias al uso de NavigationPage.
  • En la barra de navegación se muestra una imagen, el logo de la aplicación. Para poder ajustar la imagen, tamaño y posición, vamos a necesitar un Custom Renderer en el caso de iOS. Para Android, podemos utilizar layout AXML con facilidad.
  • En la barra de navegación también tenemos un botón de edición. Nada complejo que podemos abordar utilizando ToolbarItem.
  • Y llegamos al listado de opciones de perfil para elegir. La mayor característica que diferencia al listado es ele uso de columnas. Utilizaremos FlowListView.

Añadir el logo en la barra de navegación

En Android tenemos la definición de la Toolbar en Resources > layout >Toolbar.axml. Para añadir una imagen como logo y poder configurarla a nuestro antojo:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/toolbar"
 android:layout_width="match_parent"
 android:layout_height="wrap_content"
 android:background="?attr/colorPrimary"
 android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
 android:popupTheme="@style/ThemeOverlay.AppCompat.Light"
 android:elevation="5dp">
      <ImageView
           android:id="@+id/logoImageLayout"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:adjustViewBounds="true"
           android:src="@drawable/netflix" />
</android.support.v7.widget.Toolbar>

El caso de iOS es ligeramente diferente.

public class LogoPageRenderer : PageRenderer
{
     public override void ViewWillAppear(bool animated)
     {
          base.ViewWillAppear(animated);

          var image = UIImage.FromBundle("netflix.png");
          var imageView = new UIImageView(new CGRect(0, 0, 140, 70));

          imageView.ContentMode = UIViewContentMode.ScaleAspectFit;
          imageView.Image = image.ImageWithRenderingMode(UIImageRenderingMode.AlwaysOriginal);

          if (NavigationController != null)
          {
               NavigationController.TopViewController.NavigationItem.TitleView = imageView;
          }
     }
}

Creamos un sencillo Custom Renderer donde vamos a añadir un UIImageView como nuestro logo de la TitleView de UINavitationItem.

Botones en la barra de navegación

Este bloque no tiene gran complejidad ya que Xamarin.Forms cuenta con soporte directo para poder añadir y gestionar botones de la Toolbar.

<ContentPage.ToolbarItems>
     <ToolbarItem
          Icon="edit"/>
</ContentPage.ToolbarItems>

Listado con columnas

¿Cómo podemos gestionar esta necesidad?. Tenemos muchas opciones:

  • Crear un control personalizado de Layout.
  • Crear un control más simple utilizando un ScrollView y un Grid.
  • Utilizar algo existente que nos otorge esta solución.

La comunidad Xamarin y el conjunto de plugins, controles y librerías disponibles es elevada. Vamos a utilizar FlowListView.

FlowListView permite crear listados con columnas además de permitir:

  • Carga infinita.
  • Cualquier contenido como celda.
  • DataTemplateSelector.
  • Grupos.
  • Etc.

Añadimos el paquete NuGet a todos los proyectos de la solución. Debemos realizar la inicialización de FlowListView en nuestra librería compartida, App.xaml.cs:

FlowListView.Init();

A continuación, y tras declarar el espacio de nombres en la página XAML de selección de perfil:

xmlns:flv="clr-namespace:DLToolkit.Forms.Controls;assembly=DLToolkit.Forms.Controls.FlowListView"

Utilizamos el control:

<flv:FlowListView
     FlowItemsSource="{Binding Profiles}"
     FlowItemTappedCommand="{Binding HomeCommand}"
     BackgroundColor="{StaticResource BackgroundColor}"
     FlowColumnCount="2" 
     FlowColumnExpand="First"
     SeparatorVisibility="None"
     HasUnevenRows="True">
     <flv:FlowListView.FlowColumnTemplate>
          <DataTemplate>
               <templates:ProfileItemTemplate />
          </DataTemplate>
     </flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>

Definimos dos columnas con la propiedad FlowColumnCount.

Vamos a ver el resultado final de lo que llevamos, en Android:

Selección de perfil en Android

Y en iOS:

Selección de perfil en iOS

Vamos bastante bien, ¿continuamos?.

Pantalla principal

Continuamos con la pantalla principal y al igual que hicimos previamente, vamos a realizar un análisis previo de que vamos a necesitar:

  • Tenemos un menú lateral deslizante. Un patrón de navegación muy conocido y utilizado en una gran variedad de aplicaciones. En Xamarin.Forms podemos utilizar este patrón de navegación utilizando una MasterDetailPage.
  • Se continua mostrando el logo en la barra superior. Continuaremos utilizando lo previamente realizado.
  • Contamos con un botón de búsqueda en la parte superior. De nuevo, volveremos a hacer uso de ToolbarItem.
  • El contenido se caracteriza por contar con diferentes grupos o bloques de elementos a los que accedemos realizando scroll horizontal. Para conseguir este objetivo, vamos a realizar un control personal.

Menú lateral deslizante

Al navegar al apartado principal, necesitaremos utilizar una MasterDetailPage.

<MasterDetailPage 
     xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     x:Class="Xamarin.Netflix.Views.MainView" 
     xmlns:views="clr-namespace:Xamarin.Netflix.Views"
     MasterBehavior="Popover">
     <MasterDetailPage.Master>
          <views:MenuView
               BindingContext="{Binding MenuViewModel}"/>
     </MasterDetailPage.Master>
     <MasterDetailPage.Detail>
          <ContentPage 
               BackgroundColor="Transparent" />
     </MasterDetailPage.Detail>
</MasterDetailPage>

MasterDetailPage cuenta con dos propiedades fundamentales:

  • Master: Contenido del menú lateral deslizante.
  • Detail: Vista que visualizamos.

En Master vamos a definir el menú principal de Netflix un poco más adelante. En Detail, vamos a definir en una ContentPage la vista principal.

Listado horizontal

Necesitamos utilizar un listado horizontal. Ante un volumen elevado de elementos debemos de pensar en aspectos como el rendimiento (caché, reutilización del celdas, etc.). En este caso, cada listado horizontal cuenta con un volumen bajo de elementos.

Vamos a realizar un control personal con código totalmente compartido y utilizando elementos de Xamarin.Forms. Vamos a utilizar un ScrollView junto a un StackLayout apilando elementos horizontalmente. Veamos el constructor del control con la definición básica del mismo:

public HorizontalList()
{
      _scrollView = new ScrollView();
      _itemsStackLayout = new StackLayout
     {
          Padding = Padding,
          Spacing = Spacing,
          HorizontalOptions = LayoutOptions.FillAndExpand
     };

     _scrollView.Content = _itemsStackLayout;
     Children.Add(_scrollView);
}

Necesitamos definir la fuente de información, como se verá cada elemento, etc. Para ello, definimos una serie de BindableProperties:

public static readonly BindableProperty SelectedCommandProperty =
     BindableProperty.Create("SelectedCommand", typeof(ICommand), typeof(HorizontalList), null);

public static readonly BindableProperty ItemsSourceProperty =
     BindableProperty.Create("ItemsSource", typeof(IEnumerable), typeof(HorizontalList), default(IEnumerable<object>), BindingMode.TwoWay, propertyChanged: ItemsSourceChanged);

public static readonly BindableProperty SelectedItemProperty =
     BindableProperty.Create("SelectedItem", typeof(object), typeof(HorizontalList), null, BindingMode.TwoWay, propertyChanged: OnSelectedItemChanged);

public static readonly BindableProperty ItemTemplateProperty =
     BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(HorizontalList), default(DataTemplate));

A la hora de utilizar el control:

<Label
     Text="Continue watching"
     Style="{StaticResource TitleStyle}">
</Label>
<controls:HorizontalList 
     ListOrientation="Horizontal" 
     ItemsSource="{Binding Watching}"
     HeightRequest="200">
     <controls:HorizontalList.ItemTemplate>
          <DataTemplate>
               <templates:WatchingItemTemplate />
          </DataTemplate>
     </controls:HorizontalList.ItemTemplate>
</controls:HorizontalList>

Dentro de un StackLayout (apila elementos por defecto verticalmente) repetimos la misma estructura para mostrar diferentes grupos (últimas novedades, películas, series, etc.). El resultado en Android:

Vista principal en Android

Y en iOS:

Vista principal en iOS

El menú principal

¿Recuerdas que anteriormente hablamos ligeramente del menú principal al hablar de la MasterDetailPage?. Es hora de retomarlo. Características:

  • Tenemos una cabecera donde podemos ver el perfil utilizado en la aplicación.
  • A continuación, tenemos un listado con las diferentes secciones de la aplicación.
  • Hay apartados que destacan al contar con separadores entre otros elementos además de icono que refuerza su contenido.

Listado de opciones

Todos los puntos anteriores los podemos conseguir directamente utilizando un ListView junto a una ViewCell personalizada y el Header.

<ListView
     ItemsSource="{Binding MenuItems}" 
     BackgroundColor="{StaticResource MenuBackgroundColor}"
     SeparatorVisibility="None">
     <ListView.ItemTemplate>
          <DataTemplate>
               <ViewCell>
                    <templates:MenuItemTemplate />
               </ViewCell>
          </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

El separador solo aparece con algunos de los elementos. Por este motivo, y aunque el ListView cuenta con la propiedad SeparatorVisibility, creamos el separador en la plantilla que define a cada elemento donde vamos a controlar la visibilidad en base a una propiedad que define cada elemento del menú.

<Grid
     HeightRequest="48">
     <Grid.Triggers>
          <DataTrigger
               TargetType="Grid"
               Binding="{Binding IsEnabled, Mode=TwoWay}"
               Value="False">
               <Setter Property="Opacity" Value="0.6" />
          </DataTrigger>
     </Grid.Triggers>
     <Grid.ColumnDefinitions>
          <ColumnDefinition Width="2" />
          <ColumnDefinition Width="Auto" />
          <ColumnDefinition Width="*" />
     </Grid.ColumnDefinitions>
     <Grid.RowDefinitions>
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
     </Grid.RowDefinitions>
     <Image 
          Grid.Row="0"
          Grid.Column="1"
          Source="{Binding Icon}"
          Style="{StaticResource MenuItemIconStyle}"/>
     <Label 
          Grid.Row="0"
          Grid.Column="2"
          Text="{Binding Title}"
          Style="{StaticResource MenuItemTextStyle}"/>
     <Grid 
          Grid.Row="1"
          Grid.Column="0"
          Grid.ColumnSpan="3"
          HeightRequest="2"
          BackgroundColor="{StaticResource BlackColor}"
          IsVisible="{Binding Separator}"/>
</Grid>

El resultado en Android:

Menú en Android

Y en iOS:

Menú en iOS

La información detallada de un contenido

Llegamos a la vista más compleja de todas las que llevamos.

¿Por qué?.

Analicemos las necesidades:

  • La barra de navegación, donde encontraremos el botón de navegación atrás entre otras opciones, es transparente!. Vamos a necesitar un Custom Renderer para conseguir este objetivo tanto en Android como en iOS.
  • El logo… desaparece. Tenemos que gestionar este cambio. En Android usaremos código específico para acceder a la imagen de logo y jugar con su visibilidad. En iOS, también vamos a utilizar código específico pero en forma de Custom Renderer.
  • La imagen destacada del elemento (película, documental o serie) seleccionada hace efecto Parallax. Gracias a las opciones de transformación de Xamarin.Forms podremos hacer translaciones, jugar con la opacidad o escala. Es decir, podemos conseguir este efecto gestionando el scroll realizado y con código totalmente compartido.
  • Hay un listado de elementos similares mostrado en tres columnas. Aquí volveremos a hacer uso de FlowListView.

Barra transparente

Una de las características principales de la página de detalles es la barra de navegación transparente.

¿Cómo lo conseguimos?.

En Android, vamos a crear un Custom Renderer de NavigationPage.

public class CustomNavigationBarRenderer : NavigationPageRenderer
{

}

Junto al uso de mensajería utilizando FormsToolkit haremos que por defecto el color de la barra de navegación sea de color, menos el la página de detalles, que será transparente.

MessagingService.Current.SendMessage(MessageKeys.ToolbarColor, Color.Transparent);

En el renderer personalizado de la NavigationPage, vamos a verificar si el color es transparente para modificar el tamaño y posición del Layout para conseguir el efecto.

En el caso de iOS, necesitamos de nuevo un Custom Renderer.

public class TransparentNavigationBarPageRenderer : PageRenderer
{
     public override void ViewDidLayoutSubviews()
     {
          base.ViewDidLayoutSubviews();

          if (NavigationController != null)
          {
               NavigationController.NavigationBar.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
               NavigationController.NavigationBar.ShadowImage = new UIImage();
               NavigationController.NavigationBar.BarTintColor = UIColor.Clear;
               NavigationController.NavigationBar.TintColor = UIColor.White;
          }
     }
}

Modificamos el color de la barra, suprimimos la imagen de sombra para evitar cualquier tipo de separación visual entre la cabecera y el contenido para lograr la sensación de continuidad buscado.

Es momento de quitar el logo

De nuevo, hacemos uso de mensajería utilizando FormsToolkit para conseguir este objetivo en Android. Desde la vista de detalles (PCL):

MessagingService.Current.SendMessage(MessageKeys.ChangeToolbar, true);

Y nos suscribimos para recibir el mensaje desde el proyecto Android, en la actividad principal:

MessagingService.Current.Subscribe<bool>(MessageKeys.ChangeToolbar, (page, showLogo) =>
{
     var logo = FindViewById<ImageView>(Resource.Id.logoImageLayout);

     if (showLogo)
     {
          logo.Visibility = ViewStates.Visible;
     }
     else
     {
          logo.Visibility = ViewStates.Invisible;
     }
});

Accedemos a la imagen Toolbar y cambiamos la visibilidad según en caso (mostrar u ocultar).

En el caso de iOS, gracias al uso de Platform Specifics, hacemos la barra traslúcida.

var navigationPage = Parent as Forms.NavigationPage;

if (navigationPage != null)
     navigationPage.On<iOS>().EnableTranslucentNavigationBar();

Además. recuerda que previamente ya vimos el renderer de la NavigationPage aplicado a la página de detalles.

[assembly: ExportRenderer(typeof(DetailView), typeof(TransparentNavigationBarPageRenderer))]

Parallax

Sin duda, lo habrás experimentado ya sea en web o e aplicaciones móviles. Haces scroll y el fondo (o una imagen) se mueve a una velocidad distinta que el contenido, creando un ligero efecto de profundidad.

¿Cómo conseguimos esto?.

Hemos comentado que el efecto se aplica al hacer scroll. Por lo tanto, comenzamos capturando información cada vez que se realiza scroll gracias al evento Scrolled del ScrollView.

ParallaxScroll.Scrolled += OnParallaxScrollScrolled;

Tras hacer scroll vertical, la dirección puede ser en dos sentidos, hacia arriba o hacia abajo:

double translation = 0;

if (_lastScroll < e.ScrollY)
     translation = 0 - ((e.ScrollY / 2));
else
     translation = 0 + ((e.ScrollY / 2));

HeaderPanel.TranslateTo(HeaderPanel.TranslationX, translation);

Dependiendo de la dirección del scroll, se captura la cantidad de scroll realizado con e.ScrollY, para finalmente aplicar una transformación de transladación de la cabecera.

El resultado:

NOTA: Se puede modificar este efecto para conseguir potenciarlo aún más. Habitualmente también se juega con la escala y con la opacidad. Ambas opciones al igual que la transladación son posibles desde código compartido.

Listado con tres columnas

Nada «diferente» a lo ya visto previamente. Hacemos uso de FlowListView:

<flv:FlowListView
     FlowItemsSource="{Binding SimilarMovies}"
     BackgroundColor="{StaticResource BackgroundColor}"
     FlowColumnCount="3" 
     FlowColumnExpand="First"
     SeparatorVisibility="None"
     HasUnevenRows="True"
     HeightRequest="350">
     <flv:FlowListView.FlowColumnTemplate>
          <DataTemplate>
               <templates:MovieItemTemplate />
          </DataTemplate>
     </flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>

Aplicando 3 columnas con la propiedad FlowColumnCount.

El resultado en Android:

Detalles de una pelícucla en Android

Y en iOS:

Detalles en iOS

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

¿Qué plugins o componentes se han utilizado?

Se ha utilizado:

  • FFImageLoading – Con el objetivo principal de cachear imágenes. Dado el tipo de aplicación y la importancia de las imágenes, es importante. Aunque recuerda, en este ejemplo todas las imágenes son locales.
  • Xamarin Forms Toolkit – Toolkit para Xamarin.Forms con helpers, converters, etc. Se hace uso principalmente del sistema de mensajería.
  • FlowListView – ListView con soporte a columnas (entre otras características).

Conclusiones

Creo que las capturas son una buena conclusión. Logramos replicar la interfaz de usuario de una aplicación sumamente utilizada y destacada en las diferentes tiendas utilizando exclusivamente lo que proporciona Xamarin.Forms junto a plugins o componentes gratuitos por parte de la comunidad.

Tras pasar varias analíticas podemos ver que en este ejemplo, en Android se llega a compartir un 85,87% de código y un 89.53% en iOS. Podríamos seguir compartiendo gran cantidad de lógica como peticiones HTTP, gestión de errores, etc. Sin embargo, probablemente y de cara a ciertas necesidades de la aplicación, también se requerirían más Custom Renders y/o efectos. La cantidad de código compartida seguiría siendo elevada.

Y llegamos a la parte final del artículo. Es un concepto de artículo que tenía en mente y he decidido a lanzarme a ello tras ver una idea similar por varios compañeros en la comunidad. ¿Qué te parece este tipo de artículos?. Es sumamente divertido preparar una aplicación conocida e intetar «desgranar» cada pasos a realizar. Sin duda, espero repetir esta idea con otras aplicaciones aunque me gustaría saber tu opinión. ¿prefieres artículo habitual, videoblog, otro formato?.

Más información

[Xamarin.Forms] Recopilación de ejemplos con interfaz de usuario atractiva

Introducción

La evolución de Xamarin.Forms es meritoria. En los últimos tiempos se han recibido novedades interesantes como efectos, vistas nativas, Forms Embedding, etc. Sin embargo, en muchos casos se sigue asociado a desarrollos muy simples o formularios básicos.

Realmente, en el estado actual de Xamarin.Forms se pueden conseguir aplicaciones nativas de gran escala, con interfaces cuidadas y con alta integración con la plataforma.

La comunidad de Xamarin.Forms, en los últimos tiempos, ha realizado gran cantidad de ejemplos reproduciendo desde patrones de diseño muy habituales hasta aplicaciones destacadas en cada tienda de aplicaciones y muy utilizadas.

Recopilación de ejemplos

Cada ejemplo reproduciendo una aplicación real o un apartado concreto suele contener un gran conjunto de pequeños detalles, Custom Renderers o efectos. Aspectos como:

  • Añadir logo personalizado en la parte superior.
  • Barra superior transparente.
  • Personalizar mapas.
  • Etc.

El volumen de ejemplos está creciendo (¡y seguirá creciendo!) así que, ¿y si hacemos una recopilación?. Se ha creado un repositorio en GitHub donde se van a ir recopilando todos los ejemplos que busquen como objetivo mostrar como conseguir interfaces de usuario atractivas con Xamarin.Forms.

NOTA: Se buscan ejemplos de código abierto que puedan ayudar al resto de usuarios de la comunidad a conseguir resultados similares.

De cada ejemplo recopilado se buscará:

  • Incluir enlace al autor.
  • Incluir enlace a blog relacionado con el ejemplo (si existe).
  • Enlace al código fuente.
  • Imágenes, gifs o videos.
  • Descripción y principales características.

¿Qué te parece la idea?, ¿echas en falta más información en cada ejemplo?.

Más información

[Xamarin.Forms] Localización de Aplicaciones

Introducción

Cuando desarrollamos nuestras aplicaciones, un factor importante a tener en cuenta es el público objetivo de la misma. Analizando este punto debemos determinar si distribuimos nuestra aplicación a nivel nacional, en multiples países o a nivel mundial. Si decidimos la última opción debemos de tener en cuenta que aunque las probabilidades de llegar a una mayor cantidad de público aumentan considerablemente, no todo el mundo habla español por lo que seguimos restringidos.

¿Qué podemos hacer?

En este artículo vamos a realizar los pasos necesarios para localizar una aplicación Xamarin.Forms , ¿te apuntas?.

Globalización en Apps Xamarin.Forms

El proceso de globalización consiste en preparar nuestra aplicación para «todo el mundo». La aplicación debe estar preparada para mostrar la información en diferentes idiomas. Los textos y recursos de nuestra aplicación deben ser diferentes en base al idioma configurado por el dispositivo.

Archivos RESX

Para la gestión de textos en cada idioma utilizaremos archivos RESX. Tendremos un archivo de recursos diferente por cada idioma soportado. Cada archivo se basa en clave-valor. Las claves se repetirán en cada archivo mientras que el valor será la cadena localizada al idioma correspondiente.

Creando los archivos RESX

En la librería portable, comenzamos creando una carpeta Resources con el objetivo de mantener todos los archivos organizados. A continuación, crearemos un archivo de recursos por cada idioma soportado. Para crear el archivo de recursos, hacemos clic derecho sobre la recien creada carpeta Resources, Añadir > Nuevo archivo > General > Resource File.

Nuevo archivo de recursos

Nombra al archivo como Resources o AppResources (nombres habituales) para el idioma por defecto, inglés. Por cada idioma soportado, se debe repetir el proceso llamando al archivo AppResources.{nombre cultura}.resx.

NOTA: Puedes ver un listado completo de países, idiomas y su cultura en este enlace.

Añadiendo soporte al idioma español:

Español

Tras ambos archivos, si abrimos un archivo de recurso veremos algo como lo siguiente:

RESX

Añadiremos las claves en la comuna Nombre y el valor localizado a cada idioma en la columna Valor. Posteriormente, accederemos a cada valor localizado utilizando la clave. En base al idioma, se accederá a un archivo de recursos u otro. De esta forma, podremos mantener de forma sencilla los textos localizados y ofecer una aplicación localizada a diferentes idiomas.

Utilizando los archivos desde C#

Tras añadir un valor:

Añadimos valores

Podemos acceder al valor desde código C# de forma sumamente sencilla:

Label label = new Label()
{
     Text = AppResources.LanguageText
};

Si se lanza la aplicación en español y en inglés el valor estará localizado en cada idioma.

Y ahora desde XAML

Utilizar valores localizados utillizando archivos de recursos es rápido y directo desde código C#. Sin embargo, desde XAML necesitamos «algo más». Por defecto, no tenemos ninguna extensión de marcado que nos permita indicar el valor de la clave que buscamos en el archivo de recursos para permitir localizar la aplicación. Sin embargo, si podemos crear extensiones de marcado personalizadas utilizando la interfaz IMarkupExtension:

[ContentProperty("Text")]
public class TranslateExtension : IMarkupExtension
{
     const string ResourceId = "XamarinFormsLocalization.Resources.AppResources";

     public string Text { get; set; }

     public object ProvideValue(IServiceProvider serviceProvider)
     {
          if (Text == null)
               return null;

          ResourceManager resourceManager = new ResourceManager(ResourceId, typeof(TranslateExtension).GetTypeInfo().Assembly);

          return resourceManager.GetString(Text, CultureInfo.CurrentCulture);
     }
}

Utilizamos la clase ResourceManager para acceder al archivo de recurso correspondiente. A continuación, se utiliza el método GetString para obtener el valor asociado a la clave pasada como parámetro en la propiedad Text.

¿Y cómo lo utilizamos?.

Sencillo, primero declaramos el espacio de nombres en XAML necesario:

xmlns:extensions="clr-namespace:XamarinFormsLocalization.MarkupExtensions"

Y a continuación:

 <Label 
      Text="{extensions:TranslateExtension Text=LanguageText}" />

Utillizamos la extensión pasando como parámetro la clave deseada en la propiedad Text. También podemos utilizar la extensión de marcado de forma menos verbosa:

<Label 
     Text="{extensions:Translate LanguageText}" />

Otros recursos

Una aplicación móvil no se compone exclusivamente de textos. Existen otra serie completa de recursos muy utilizados. Entre los recursos más utilizados, imágenes.

Normalmente se cuentan con imágenes que se pueden ver exactamente igual en diferentes idiomas, pero no así con todas.

¿Cómo gestionamos la localización de imágenes por idioma?

Es una tarea que debemos realizar en cada plataforma de forma específica.

Android

Utilizamos la carpeta drawable dentro de Resources para la gestión de imágenes. Para contar con imágenes diferentes por idioma, podemos crear carpetas con el nombre drawable-{código del idioma}. Android se basa en esta estructura de carpetas con sufijos para localizar imágenes.

Imágenes localizadas en Android

NOTA: Para soportar diferentes imágenes para resoluciones diferentes y además basándonos en el idioma, seguimos utilizando la misma estructura de sufijos en carpetas. Por ejemplo: drawable-es-xdpi.

iOS

En este caso, iOS, utiliza Localization Projects o .lproj para contener imágenes por idioma junto con recursos de cadenas. Dentro de la carpeta Resources debemos crear {códido de idioma}.lproj por cada idioma soportado.

Localización de imágenes en iOS

Cada carpeta correspondiente a un idioma contará con sus versiones de imágenes. En caso de que una carpeta de un idioma no cuenta con imágenes, se utilizarán las correspondientes del idioma por defecto.

NOTA: Para pantallas retina se pueden añadir imágenes @2x y @3x.

UWP

En el caso de aplicaciones universales, organizaremos dentro de la carpeta Assets los recursos por idioma en carpetas con el nombre de la cultura.

Localización de imágenes en UWP

No tenemos un mecanismo que automáticamente seleccione la imagen correspondiente a cada idioma. Por este motivo, vamos a crear un sencillo Custom Renderer.

xmlns:extensions="clr-namespace:XamarinFormsLocalization.MarkupExtensions"
[assembly: ExportRenderer(typeof(Image), typeof(XamarinFormsLocalization.UWP.Renderers.LocalizedImageRenderer))]
namespace XamarinFormsLocalization.UWP.Renderers
{
     public class LocalizedImageRenderer : ImageRenderer
     {
          protected override void OnElementChanged(ElementChangedEventArgs<Image> e)
          {
               base.OnElementChanged(e);

               if (e.NewElement != null)
               {
                    var fileImageSource = e.NewElement.Source as FileImageSource;

                    if (fileImageSource != null)
                    {
                         var fileName = fileImageSource.File;
                         string currentUICulture = CultureInfo.CurrentUICulture.ToString();
                         e.NewElement.Source = Path.Combine("Assets/" + currentUICulture + "/" + fileName);
                    }
               }
          }
     }
}

Creamos un Custom Render en UWP del control Image. Se obtiene la ruta de la imágen a la que se añade la ruta correcta utilizando CurrentUICulture. De esta forma, automáticamente, en base al idioma se accederá a una ruta u otra.

El resultado de la aplicación en inglés:

Inglés

Y en español:

Español

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Existen otra serie de puntos interesantes relacionados con la localización de aplicaciones como por ejemplo la localización del nombre de la aplicación que veremos en otro artículo.

Recuerda, cualquier duda o comentario es bienvenido!

Más información

[Xamarin.Forms] Cambios en la clase Device: RuntimePlatform

Introducción

Xamarin.Forms añade una capa de abstracción sobre la capa de la interfaz de usuario permitiendo definir la misma una única vez siendo válida para todas las plataformas.

Xamarin.Forms

A pesar de definir la interfaz de usuario una única vez para todas las plataformas, tenemos la posibilidad de realizar personalizaciones y adaptaciones para ofrecer la mejor experiencia posible en cada una de las plataformas soportadas.

La clase Device

La clase Device sin duda alguna es una de las claves a la hora de personalizar la interfaz de usuario por plataforma. Esta clase contiene diversas propiedades y métodos para personalizar la UI y la funcionalidad en base a la plataforma.

TargetPlatform

Previamente ya conocimos en el blog a esta clase. ¿Por qué volvemos a revisarlo?. Sencillo, en el proceso de actualización de Xamarin.Forms normalmente se incluyen novedades sin afectar a lo existente y en ocasiones, como esta, afectan a algo existente.

Dentro de la clase Device, lapropiedad OS permitía identificar la plataforma en la que se ejecuta la aplicación. Podía ser un valor de la enumeración TargetPlatform. Sin embargo, si lo utilizas verás que te indica que es algo obsoleto. En lugar de OS, ahora se debe utilizar RuntimePlatform que puede tener un valor de la enumeración TargetPlatform con uno de los siguientes valores:

  • Android
  • iOS
  • macOS
  • UWP

NOTA: Ante la futura llegada de nuevos backends como GTK# o WPF, es de esperar recibir nuevos valores en TargetPlatform.

if(Device.RuntimePlatform == Device.iOS)
{
     this.Padding = new Thickness (0, 12, 0, 0);
}

¿Y en XAML?

La interfaz de usuario de las aplicaciones Xamarin.Forms podemos definirla en C# o desde código XAML. Desde XAML también podemos realizar personalizaciones en base a la plataforma utilizando el método OnPlatform.

OnPlatform

De forma similar, Device.OnPlatform ha sido reemplazado por OnPlatform y las APIs On. Vienen a facilitar las mismas posibilidades vistas previamente pero en XAML.

<StackLayout.Margin>
      <OnPlatform x:TypeArguments="Thickness">
           <On Platform="Android, iOS" Value="0,20,0,0" />
           <On Platform="Windows" Value="0,0,0,0" />
      </OnPlatform>
</StackLayout.Margin>

NOTA: Como puedes ver, se utilizan cadenas para especificar cada plataforma. Ante un valor incorrecto de plataforma ningún error o excepción se lanzará. Sencillamente no se ejecutará el código específico de la plataforma que se aplica incorrectamente.

Más información

[Xamarin.Forms] MacOS Preview

Introducción

La evolución de Xamarin.Forms en los últimos años ha sido más que sorprendente. De igual forma que el interés y crecimiento de uso. Partía de una base sin duda atractiva, una capa de abstracción a nivel de UI que permite crear con el mismo código, C# o XAML, la interfaz de usuario en iOS, Android y Windows. Versión tras versión ha logrado llegar a permitir más posibilidades mejorando el soporte de cada plataforma, las posibilidades en XAML o el rendimiento (cabe destacar las mejoras en este punto). Hoy día se pueden hacer aplicaciones con resultados bastante sorprendentes.

Con la futura versión 3.0 en camino, entre las futuras mejoras encontramos la llegada de nuevos backends o lo que es lo mismo nuevas plataformas como WPF (Windows) o Linux (GTK#). Sin embargo, hoy día ya tenemos disponible como Preview una nueva plataforma, macOS.

macOS

En este artículo vamos a añadir un proyecto macOS y Xamarin.Forms al mismo. Veremos que con pocos sencillos pasos podemos lograr tener una aplicación para una nueva plataforma.

Configurar el proyecto macOS

Hasta tener plantillas de proyectos Xamarin.Forms con aplicación Cocoa, comenzaremos creando Mac > App > Cocoa App en Visual Studio para macOS.

Nueva App Cocoa

NOTA: Al igual que el desarrollo con Xamarin.Mac, el desarrollo para macOS requiere de un Mac.

Tras crear el proyecto, debemos añadir una librería portable (PCL) donde añadiremos el código común compartido con otros posibles backends Xamarin.Forms. Tras añadir el proyecto de la PCL, debemos añadir la referencia de la misma en el proyecto macOS.

A continuación, añadimos en ambos proyectos el paquete NuGet de Xamarin.Forms:

Paquetes NuGet de Xamarin.Forms

NOTA: La versión del paquete NuGet debe ser Xamarin.Forms 2.3.5.233-pre1 o superior.

La aplicación Cocoa viene con un Storryboard para configurar la interfaz de usuario y ciertos parámetros que no vamos a utilizar. Debemos editar el archivo Info.plist para eliminar la entrada NSMainStoryboardFile:

Modificar Info.plist

Llega el momento de realizar pequeños cambios de código para cargar la aplicación Xamarin.Forms. Comenzamos editando Main.cs:

NSApplication.SharedApplication.Delegate = new AppDelegate();

Inicializamos AppDelegate. En el delegado de la aplicación, vamos a inicializar Xamarin.Forms, crear una ventana y cargar la aplicación Xamarin.Forms.

public override void DidFinishLaunching(NSNotification notification)
{
     Forms.Init();
     LoadApplication(new App());
     base.DidFinishLaunching(notification);
}

Todo listo!

Probar la App

TipCalc macOS

Tenéis el código fuente del ejemplo utilizado disponible en GitHub:

Ver GitHub

Personalización por platafoma

La clase Device sin duda alguna es una de las claves a la hora de personalizar la interfaz de usuario por plataforma. Esta clase contiene diversas propiedades y métodos para personalizar la UI y la funcionalidad en base a la plataforma o el factor de forma.

Contamos con la propiedad RuntimePlatform que cuenta con un valor de una enumeración con cada una de las plataformas soportadas. Se ha incluido macOS y ahora tenemos también la posibilidad de crear código condicional para macOS en Xamarin.Forms:

if(Device.RuntimePlatform == Device.macOS)
{

}

Más información

[Xamarin.Forms] Aplicación móvil y Cosmos DB

Introducción

En una gran variedad de aplicaciones móviles se requiere almacenar datos. Podemos almacenar datos locales con opciones como SQLite o Realm; o en la nube con diferentes opciones. Entre las opciones disponibles en Azure, ha llegado con fuerza Cosmos DB.

En este artículo vamos a ver paso a paso como utilizar una base de datos de documentos Cosmos DB en una aplicación Xamarin.Forms. Realizaremos una aplicación que permita tomar, modificar y eliminar notas.

Cosmos DB

Una base de datos de documentos Azure Cosmos DB es una base de datos No SQL que proporciona acceso a información (JSON) de forma rápida, con alta disponibilidad, escalable y con replicación global.

Una base de datos de documentos en Azure Cosmos DB es un contenedor lógico para colecciones de documentos y usuarios. Puede contener cero o más colecciones. A su vez, cada colección de documentos puede tener un nivel de rendimiento diferente en base a la frecuencia de acceso. Cada colección de documentos cuenta con documentos JSON. A medida que se añaden documentos a una colección, Cosmos DB indexa automáticamente.

NOTA: Para pruebas y desarrollo, una base de datos de documentos se puede utilizar con un emulador. El emulador permite probar de forma local sin necesidad de suscripción Azure ni costes.

Crear Azure Cosmos DB

Accede al portal de Azure. Desde el menú lateral, Databases > Azure Cosmos DB:

Nueva Azure Cosmos DB

A continuación, debemos rellenar el formulario de creación:

Crear CosmosDB Azure

Donde:

  • API: Existen cuatro opciones diferentes: Gremlin (graph), MongoDB, SQL (DocumentDB), y tablas (key-value). En nuestro ejemplo usaremos DocumentDB.

Preparando el proyecto

Partiendo de una aplicación Xamarin.Forms, lo primero que necesitamos para realizar la integración con Azure Cosmos DB es añadir el paquete NuGet DocumentDB Client Library a la solución.

Microsoft.Azure.DocumentDB

La interfaz ded usuario

En nuestra aplicación contaremos con dos vistas, un listado de notas y una vista de detalles para crear, editar o eliminar una nota específica.

Comenzamos definiendo la vista principal. Tendremos un listado de notas:

<ListView 
     ItemsSource="{Binding Items}" 
     SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
     RowHeight="60">
     <ListView.ItemTemplate>
          <DataTemplate>
               <ViewCell>
                    <ViewCell.View>
                         <StackLayout 
                              Padding="12,0,12,0" 
                              HorizontalOptions="FillAndExpand">
                              <Label 
                                   Text="{Binding Notes}"
                                   FontSize="Medium"
                                   FontAttributes="Bold"/>
                              <Label 
                                   Text="{Binding Date}"
                                   FontSize="Small"/>
                         </StackLayout>
                     </ViewCell.View>
                </ViewCell>
           </DataTemplate>
      </ListView.ItemTemplate>
</ListView>

A parte de definir como se visualizará cada elemento de la lista definiendo el DataTemplate establecemos la fuente de información, propiedad ItemsSource enlazada a propiedad de la ViewModel que obtendrá los datos de la base de datos.

Además del listado, debemos añadir en nuestra interfaz una forma de poder insertar nuevas tareas. Para ello, una de las opciones más habituales e idóneas es utilizar una Toolbar.

 <ContentPage.ToolbarItems>
      <ToolbarItem
           Name="Add" 
           Command="{Binding AddCommand}">
           <ToolbarItem.Icon>
                <OnPlatform 
                     x:TypeArguments="FileImageSource"
                     Android="plus" 
                     iOS="plus.png" />
           </ToolbarItem.Icon>
      </ToolbarItem>
 </ContentPage.ToolbarItems>

La clase Device es muy importante en Xamarin.Forms ya que nos permite acceder a una serie de propiedades y métodos con el objetivo de personalizar la aplicación según dispositivo y plataforma. Además de permitirnos detectar el tipo de dispositivo, podemos detectar la plataforma gracias a la enumeración Device.OS o personalizar elementos de la interfaz gracias al método Device.OnPlatform entre otras opciones. En nuestro ejemplo, personalizamos el icono de añadir en base a la plataforma.

Nuestra interfaz:

Listado de notas

Enlazamos la View con la ViewModel estableciendo una instancia de la ViewModel a la propiedad BindingContext de la página.

BindingContext = App.Locator.NoteItemViewModel;

En la ViewModel contaremos con una propiedad pública para definir el listado de notas, además de la nota seleccionada (utilizada para la navegación):

private ObservableCollection<Note> _items;
private Note _selectedItem;

public ObservableCollection<Note> Items
{
     get { return _items; }
     set
     {
          _items = value;
          OnPropertyChanged();
     }
}

public Note SelectedItem
{
     get { return _selectedItem; }
     set
     {
          _selectedItem = value;
          _navigationService.NavigateTo<NoteItemViewModel>(_selectedItem);
     }
}

Añadimos elementos con un comando disponible en la ViewModel.

public ICommand AddCommand
{
     get { return _addCommand = _addCommand ?? new Command(AddCommandExecute); }
}

Al pulsar y lanzar el comando, navegaremos a la vista de detalles.

_navigationService.NavigateTo<NoteItemViewModel>(todoItem);

Si creamos un nuevo elemento pasaremos como parámetro una nueva entidad de tipo Note, en caso de seleccionar una existente, pasaremos el seleccionado disponible en la propiedad SelectedItem.

Definimos la interfaz de la vista de detalles:

<StackLayout 
     VerticalOptions="StartAndExpand" 
     Padding="20">
     <Label 
          Text="Notes" />
     <Entry 
          Text="{Binding Notes}"/>
     <Button 
          Text="Save"
          Command="{Binding SaveCommand}"/>
     <Button 
          Text="Delete"
          Command="{Binding DeleteCommand}"/>
     <Button 
          Text="Cancel"
          Command="{Binding CancelCommand}"/>
</StackLayout>

El resultado:

Detalles

Para enlazar la información de un elemento seleccionado, debemos capturar la información enviada en la navegación.

¿Cómo capturamos el elemento seleccionado en la navegación?. Utilizamos el método OnAppearing para capturar el parámetro NavigationContext.

public override void OnAppearing(object navigationContext)
{
     var note = navigationContext as Note;

     if (note != null)
     {
          Id = note.Id;
          Notes = note.Notes;
     }

     base.OnAppearing(navigationContext);
}

En cuanto a cada botón, cada uno de ellos estará enlazado a un comando:

private ICommand _saveCommand;
private ICommand _deleteCommand;
private ICommand _cancelCommand;

public ICommand SaveCommand
{
     get { return _saveCommand = _saveCommand ?? new Command(SaveCommandExecute); }
}

public ICommand DeleteCommand
{
     get { return _deleteCommand = _deleteCommand ?? new Command(DeleteCommandExecute); }
}

public ICommand CancelCommand
{
     get { return _cancelCommand = _cancelCommand ?? new Command(CancelCommandExecute); }
}

private void SaveCommandExecute()
{
 
}
 
private void DeleteCommandExecute()
{
 
}
 
private void CancelCommandExecute()
{
 
}

Consumiendo la base de datos Cosmos DB

Vamos a crear un servicio que contenga toda la lógica necesaria para trabajar con Cosmos DB desde nuestra aplicación Xamarin.Forms. DocumentClient encapsula la lógica necesaria relacionada con credenciales y conexiones con Azure Cosmos DB.

Comenzamos creando una instancia de DocumentClient:

var client = new DocumentClient(new Uri(EndpointUri), PrimaryKey);

En el constructor del cliente debemos pasar tanto la URL de conexión como la clave primaria. Ambos valores se pueden obtener desde el portal de Azure.

Clave Primaria

Con el cliente preparado, tenemos todo lo necesario para trabajar con Azure Cosmos DB.

Creando base de datos

De las primeras acciones a realizar sera la creación de la base de datos. Recuerda, una base de datos de documentos es un contenedor lógico para colecciones de documentos. Podemos crearla tanto desde el portal Azure como por código. Utilizaremos el método CreateDatabaseIfNotExistsAsync:

await _client.CreateDatabaseIfNotExistsAsync(new Database
{
     Id = databaseName
});

El método crea la base de datos con el identificador establecido en Id o bien, devuelve la base de datos si ya existe. Se devuelve Task<ResourceResponse<Database>> que contiene un código de estado, ideal para verificar si la base de datos se ha creado o si se ha obtenido una previamente existente.

Crear colección de documentos

Una colección de documentos es un contenedor de documentos JSON. Al igual que la base de datos, podemos crearlo en el portal o por código. Por código utilizamos CreateDocumentCollectionIfNotExistsAsync:

 await _client.CreateDocumentCollectionIfNotExistsAsync(
      UriFactory.CreateDatabaseUri(databaseName),
      new DocumentCollection
      {
           Id = collectionName
      });

El método requiere dos parámetros obligatorios (también cuenta con otros opcionales):

  • Nombre de base de datos: Especificado como Uri.
  • DocumentCollection: Representa una colección de documentos. Especificamos el nombre utilizando la propiedad Id.

El método crea la colección si no existe o devuelve la colección si ya existía previamente.

Obtener documentos de una colección

El contenido de una colección se puede obtener ejecutando una consulta. Podemos crear consultas utilizando el método CreateDocumentQuery. Por ejemplo:

var query = _client.CreateDocumentQuery<T>(_collectionLink) 
     .AsDocumentQuery();

while (query.HasMoreResults)
{
     items.AddRange(await query.ExecuteNextAsync<T>());
}

De esta forma podemos obtener todos los documentos de una colección específica. En el método CreateDocumentQuery se puede especificar vía Uri la colección de datos a la que conectar.

El método AsDocumentQuery convierte el resultado de la creación de la consulta en un objeto de tipo IDocumentQuery<T>. ExecuteNextAsync vamos obteniendo los resultados de la siguiente página (mientras exista más valores HasMoreResults).

Crear documento

Crear documentos cuyo contenido es en formato JSON se pueden insertar en una colección utilizando CreateDocumentAsync:

await _client.CreateDocumentAsync(_collectionLink, item);

Se especifica vía Uri la colección en la que se va a insertar el documento y el argumento de tipo objeto. El objeto es la información a insertar.

Eliminar documento

Se pueden eliminar documentos utilizando DeleteDocumentAsync:

await _client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseName, CollectionName, id));

Se especifica vía Uri el documento de la colección a eliminar.

NOTA: También es posible eliminar toda una colección de documentos con DeleteDocumentCollectionAsync e incluso una base de datos completa con DeleteDatabaseAsync.

Llegados a este punto, ¿qué nos falta?. Sencillo, utilizar la lógica para interactuar con Azure Cosmos DB desde nuestras ViewModels. Crearemos la base de datos y la colección al navegar a la primera vista. En esta primera vista obtendremos todos los documentos creados (nuestras notas!). Desde la vista de detalles, en cada comando enlazado a la interfaz, utilizaremos la lógica para crear o eliminar notas.

Tenéis el código fuente disponible e GitHub:

Ver GitHubTe animo a descargar el ejemplo y realizar tus propias pruebas. Hasta aquí llegamos!. Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

[Xamarin.Forms] Extensiones de marcado personalizadas

Introducción

Las extensiones de marcado son una forma de obtener un valor que no sea específico de tipo primitivo o un objeto XAML. Mediante la apertura y cierre de llaves, se define en su interior lo que se conoce como extensión de marcado.

En este artículo, vamos a conocer como crear nuestras propias extensiones de marcado.

RECUERDA: Ya vimos en un artículo anterior el concepto de extensión de marcado así como las extensiones de marcado disponibles.

 

Extensiones de marcado personalizadas

Las extensiones de marcado implementan la interfaz IMarkupExtension. Esta interfaz sólo cuenta con un método a implementar:

public object ProvideValue(IServiceProvider serviceProvider)

Cuando se realiza el análisis del código XAML y el compilador encuentra una extensión de marcado, se lanza este método antes de establecer el valor de la extensión.

Crear extensiones

Vamos a realizar una de las extensiones de marcado más sencillas posibles con el objetivo de centrarnos a fondo en los conceptos clave de la extensión. Nuestra extensión nos permitirá utilizar colores RGB estableciendo directamente los valores para rojo, verde y azul.

En el método ProvideValue, por defecto, no obtenemos valores con IServiceProvider. Por lo tanto, para obtener los valores necesarios en nuestra extensión (rojo, verde y azul) vamos a necesitar utilizar propiedades.

public class RGBColor : IMarkupExtension
{
     public int Red { get; set; }

     public int Green { get; set; }

     public int Blue { get; set; }

     public object ProvideValue(IServiceProvider serviceProvider)
     {
          return Color.FromRgb(Red, Green, Blue);
     }
}

Creamos una clase que hereda de IMarkupExtension, implementamos el método ProvideValue que se lanzará a la hora de evaluar la expresión. Gracias a propiedades capturamos los valores deseados. La lógica del método ProvideValue se encarga de crear el valor devuelto deseado, un color.

Todo listo!

Utilizar extensiones

Utilizar la extensión es sencillo. Necesitamos acceder a la misma, por lo tanto, debemos declarar el espacio de nombres en XAML en el lugar a utilizar:

xmlns:extensions="clr-namespace:CustomMarkupExtension.Extensions;assembly=CustomMarkupExtension"

Y utilizarlo:

<Label
     Text="Test"
     TextColor="{extensions:RGBColor Red=255, Green=0, Blue=255}"
     BackgroundColor="{extensions:RGBColor Red=200, Green=100, Blue = 0}" />

Fíjate que todo el código queda bastante simple y muy legible. Tenéis el código fuente del ejemplo utilizado disponible en GitHub:

Ver GitHubMediante la implementación de IMarkupExtension podemos crear nuestras propias extensiones de marcado. Las extensiones de marcado personalizadas nos dan una forma sencilla y potente de ejecutar código, realizar validaciones y conversiones de valores antes de establecer el valor de una propiedad.

Más información

[Xamarin.Forms] Extensiones de marcado

Introducción

Xamarin.Forms añade una capa de abstracción en la capa de UI que nos permite definir la misma una única vez para todas las plataformas. Podemos definir esta interfaz con código C# o XAML.

A la hora de trabajar con la interfaz en XAML con casi toda seguridad utilizarás alguna extensión de marcado.

Extensiones de marcado

Las extensiones de marcado son una forma de obtener un valor que no sea específico de tipo primitivo o un objeto XAML. Mediante la apertura y cierre de llaves, se define en su interior lo que se conoce como extensión de marcado.

Existen extensiones de marcados muy usadas y conocidas como Binding o StaticResource. Pero… ¿conoces todas las extensiones disponibles y su uso?.

En este artículo, vamos a conocer todas las extensiones básicas existentes.

Binding

Estamos ante la expresión de marcado más utilizada. Cuando el código XAML se encuentra con un literal entre llaves, lo interpreta y no se limita a mostrarlo como si fuera un simple texto. En este caso hace una evaluación encontrando la palabra reservada Binding, lo que indica que debe enlazar el valor de una propiedad. Pero…¿cómo sabe dónde se encuentra la propiedad?, todos los elementos que componen la interfaz de usuario descienden de una importante jerarquía de clases base que otorgan funcionalidad vital.

Contamos con la propiedad BindingContext que actúa como contexto de datos del elemento. Si la propiedad fuese nula en el elemento, se buscaría de forma ascendente en la jerarquía de elementos. Es decir, si la propiedad BindingContext del elemento visual es nula, buscará en el elemento padre inmediato, etc.

Veamos un ejemplo.

public class Person
{
     public string Name { get; set; }
     public string SurName { get; set; }
}

Establecemos el BindingContext:

BindingContext = person;

A la hora de utilizar el Binding:

<Label Text={Binding Name}" />

En un enlace a datos, la palabra reservada Mode indica la forma en la que se comporta el mismo. En Xamarin.Forms contamos con los siguientes modos:

  • OneWay: es el valor por defecto. Indica que el enlace se realiza en un único sentido, de lectura. Aunque el valor del elemento visual cambie, no viajará hasta el  ViewModel.
  • OneWayToSource: en un único sentido generalmente de la View al ViewModel.
  • TwoWay: en este caso el enlace es bidireccional. La View toma el valor del ViewModel, pero si lo editamos en la vista, el cambio se envía de vuelta al ViewModel.
  • Default: el modo utilizado por defecto, que como hemos visto es OneWay.
<Entry Text={Binding Username, Mode=TwoWay}" />

Por otro lado, en ocasiones, lo que se desea mostrar en la interfaz de usuario no equivale al valor enlazado. Podríamos extender modelos añadiendo más propiedades para este propósito, pero no es lo ideal. Para solventar este problema contamos con las propiedades de formateo de texto.

<Label Text="{Binding Count, StringFormat='Number of Records = {0:N}'}"/>

Veamos otro ejemplo:

<Label Text="{Binding Date, StringFormat='{0:dd MM yyyy}'}" />

Converters

En ocasiones, el valor enlazado no es el deseado. Por ejemplo, tenemos enlazado un valor de tipo DateTime pero deseamos mostrar una cadena de texto al usuario con un formato de fecha aplicado. Justo para este objetivo, tenemos los TypeConverters.

En lugar de extender nuestros modelos con más y más valores, los Converters nos permiten transformar unos valores a otros directamente desde XAML.

<Label Text="{Binding Date}" />

En este caso veríamos el resultado de lanzar el método ToString del DateTime. Podemos crear un Converter:

public class DatetimeToStringConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          if (value == null)
               return string.Empty;

          var datetime = (DateTime)value;
          return datetime.ToLocalTime().ToString("g");
     }

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          throw new NotImplementedException(); 
     }
}

El Converter es una clase que herede de la interfaz IValueConverter, se implementa el método Convert y ConvertBack. Para utilizar el converter se debe definir primero como recurso:

<ResourceDictionary>
 <local:DatetimeToStringConverter x:Key="DateTimeConverter" />
</ResourceDictionary>

Y posteriormente, utilizar la palabra reservada Converter para acceder al mismo:

<Label Text="{Binding Date, Converter={StaticResource DateTimeConverter}}" />

NOTA: Se suelen utilizar los Converters para transformaciones más complejas.

TemplateBinding

Con la versión 2.1.0 de Xamarin.Forms se introdujo el concepto de Control Templates y también el de TemplateBinding. Un TemplateBinding es similar a un Binding, excepto que el origen de un TemplateBinding siempre se establece automáticamente en el padre de la vista de destino que posee la plantilla de control.

<ControlTemplate x:Key="PageTemplate">
     <StackLayout>
          <Label Text="{TemplateBinding BindingContext.HeaderText}" /> 
          <ContentPresenter />
     </StackLayout>
</ControlTemplate>

StaticResource

En cada elemento visual podemos definir un conjunto de recursos. Utilizamos ResourceDictionary para a gestión de recursos. Con StaticResource podemos acceder a objetos estáticos definidos como recursos y disponibles en tiempo de compilación.

<ContentPage.Resources>
 <ResourceDictionary>

     <Style x:Key="ErrorLabelStyle" TargetType="Label">
          <Setter Property="TextColor" Value="Red" />
     </Style>

</ResourceDictionary>
</ContentPage.Resources>

<Label Text="Hello!" Style="{StaticResource ErrorLabelStyle}" />

DynamicResource

Esta extensión de marcado es sumamente similar a la anterior pero cuenta con una gran diferencia. DynamicResource permite acceder a recursos añadidos en tiempo de ejecución. Es decir, si un recurso es eliminado / añadido en un diccionario de recursos en tiempo de ejecución, DynamicResource es tu extensión.

var errorLabelStyle = new Style(typeof(Label));

errorLabelStyle.Setters.Add(new Setter() 
{ 
     Property = Label.BackgroundColorProperty, Value = Color.Red 
});

this.Resources.Add(errorLabelStyle);

Tras añadir dinámicament el tiempo de ejecución el estilo, accedemos al mismo:

<Label Text="Hello!" Style="{DynamicResource ErrorLabelStyle}" />

Otras extensiones de marcado

Existen estensiones de marcado intrínsecas de XAML y soportadas en archivos XAML de Xamarin.Forms.

x:Array

Se pueden definir arrays directamente en XAML gracias a esta extensión de marcado.

<x:Array x:Key="StringArray" Type="{x:Type x:String}">
     <x:String>Hello</x:String>
     <x:String>World</x:String>
</x:Array>

x:Null

Extensión de marcado que permite establecer valor nulo. Si una propiedad cuenta con soporte a valores no nulos y se desea establecer por defecto a nulo, {x:Null} es la extensión de marcado idónea.

x:Reference

Los enlaces de datos se pueden definir para vincular propiedades de dos elementos visuales en la misma página. En este caso, se establece el  BindingContext del objeto de destino utilizando la extensión de marcado x: Reference.

<StackLayout>
     <Slider 
          x:Name="slider"
          Maximum="100" />
     <Label 
          BindingContext="{x:Reference slider}"
          Text="{Binding Value,
          StringFormat='The angle is {0:F0} degrees'}" />
 </StackLayout>

x:Static

A pesar de lo que pueda dar a pensar, x:Static y StaticResource NO son iguales. Mientras que StaticResource nos permite acceder a un objeto definido en un diccionario de recursos, x:Static nos permite el acceso a una propiedad o constante estática.

<Label 
     Text="Awesome!"
     TextColor="{x:Static Color.Blue}" />

NOTA: Esta extensión de marcado es más potente de lo que pueda parecer. También puedes acceder a campos o propiedades estáticas de tu propio código. Si se cuenta con una clase estática con constantes, se podrían utilizar.

x:Type

Permite establecer el tipo del objeto utilizando {x:Type Class}.

Extensiones de marcado personalizadas

Llegamos hasta aquí!. Cuando se comienza a desarrollar en XAML algunas son muy utilizadas y bien conocidas pero no así con todas. Xamarin.Forms también permite crear nuevas extensiones de marcado personalizadas aunque es algo que veremos en un próximo artículo.

Hasta la próxima!

Más información

[Tips and Tricks] Librería para reutilizar estilos entre diferentes Apps Xamarin.Forms

Introducción

En toda aplicación móvil la apariencia visual es vital. Cada vez es mayor el esfuerzo depositado a la hora de crear aplicaciones atractivas a la par que intuitivas y en muchos casos conseguir una imagen única que diferencia a la Aplicación del resto es prioritario. Por este motivo, debemos de contar con opciones sencillas de poder personalizar los distintos elementos que componen la interfaz.

Los estilos permitir definir múltiples propiedades visuales de elementos de la interfaz de forma reutilizable.

En ocasiones, se desarrollan diferentes aplicaciones para la misma empresa o marca comercial. Hablamos de aplicaciones totalmente diferentes pero que sin duda, van a compartir algunos elementos visuales específicos como colores, logos e incluso algunos estilos visuales (forma de botones, etc.).

¿Cómo lograr reutilizar estilos entre diferentes aplicaciones Xamarin.Forms?

ResourceDictionary

Los recursos XAML son objetos que podemos reutilizar más de una vez. Hablamos desde un sencillo color o tamaño de fuente, a el uso de estilos. Los diccionarios de recursos o ResourceDictionaries permiten definir una zona donde definir recursos.

Podemos definir recursos a nivel de elemento visual, a nivel de página e incluso a nivel de Aplicación.

¿Cómo definimos recursos en otra librería?.

Buscamos tener una librería con recursos, estilos y/o converters que sea compartida entre diferentes aplicaciones:

Librería

  • Aplicación 1
  • Aplicación 2

Creando una librería

Comenzamos creando una librería portable (PCL). En Visual Studio, nuevo proyecto de tipo PCL:

PCL

Añadimos el paquete NuGet de Xamarin.Forms. Y a continuación, añadimos una página de tipo ContentPage:

ContentPage

Renombramos ContentPage por ResourceDictionary en la página recien creada.

Todo listo!.

Bastará con añadir recursos, estilos o converters. Ejemplo:

<Style x:Key="SharedButtonStyle" TargetType="Button">
     <Setter Property="BackgroundColor" Value="Red" />
     <Setter Property="Rotation" Value="45" />
</Style>

Utilizando la librería

Para utilizar la librería desde una aplicación Xamarin.Forms, comenzamos añadiendo la referencia a la librería desde la aplicación:

Referencias

Añadida la referencia, llega el paso de mayor importancia. A nivel de Aplicación, tenemos la posibilidad de añadir recursos en un diccionario de recursos:

<Application.Resources>
     <ResourceDictionary>

     </ResourceDictionary>
</Application.Resources>

Vamos a utilizar la propiedad MergedWith para añadir a los estilos de la aplicación, los estilos de la librería:

<Application
     xmlns:styles="clr-namespace:Styles;assembly=Styles">
    <Application.Resources>
        <ResourceDictionary MergedWith="styles:ButtonStyles">

        </ResourceDictionary>
    </Application.Resources>
</Application>

El resultado:

Estilos desde otra librería!

Realizamos una combinación de estilos a nivel de elemento visual, página, Aplicación e incluso desde otra librería (último botón). Tenéis el código fuente del ejemplo utilizado disponible en GitHub:

Ver GitHubRecuerda, cualquier tipo de duda o sugerencia es bienvenida en los comentario del artículo.

Más información

[Material] Xamarin Dev Days Málaga 2017

El evento

El pasado sábado 24 de Junio, tenía lugar en Málaga el Xamarin Dev Days. Una jornada con varias sesiones técnicas, taller, regalos y mucho networking.

El resultado fue un fantástico día de desarrollo Xamarin con muchas preguntas, ayuda y tiempo para charlar entre todos rodeados de un café o unas pizzas.

El material

Pude participar en el evento con una de las sesiones. Nos centramos en el desarrollo de aplicaciones móviles multiplataforma utilizando Xamarin y aprovechando la potencia de Azure.

Tras las sesiones, realizamos un muy divertido taller donde paso a paso, desarrollamos una aplicación Xamarin.Forms aplicando MVVM y accediendo a una Azure Mobile App.

En cuanto a las demos técnicas realizadas, las tenéis disponible en GitHub:

Ver GitHub

Un día genial de comunidad con grandes momentos que esperamos repetir sin duda!

Más información