[Xamarin.Forms Challenge] My Trip Countdown

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, FlexLayout, 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 un diseño de Dribble, que intentaremos replicar con Xamarin.Forms paso a paso.

Countdown Timer

Lo primero que debemos realizar, es un breve análisis de la pantalla:

  • Simplificando, estamos ante un contador hacia atrás, en concreto, hacia una fecha. En Xamarin.Forms contamos con la clase Device, una clase importante que nos facilita información importante como si estamos en una plataforma u otra, o si ejecutamos la App en teléfono o tableta. Además, Device cuenta con un Timer que podemos utilizar para la cuenta atrás, entre otras opciones.
  • De lo que más llama la atención de la simple interfaz de usuario, son los degradados. La línea de progreso circular es candidata a realizarse con SkiaSharp. A pesar de poder abordarla con un Custom Renderer, SkiaSharp nos facilitará dibujarla una única vez para todas las plataformas. El botón, a pesar de poder abordarlo también con SkiaSharp, vamos a realizarlo en nativo para poder contar con los diferentes estados, etc. En este caso, vamos a necesitar Custom Renderer o plugin de comunidad.
  • La carátula circular no es compleja. Entre en conjunto de posibilidades que tenemos, FFImageLoading gana enteros por su facilidad de uso, opciones de cache además de transformaciones.
  • La rotación de la carátula es una sencilla animación de rotación. Xamarin.Forms cuenta con una API de animaciones bastante completa.

¿Manos a la obra?

Imagen circular

Comenzamos por una de las partes principales de la vista, la imagen circular. Vamos a utilizar FFImageLoading junto a las opciones de transformación disponibles:

xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:fftransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"

Vamos a utilizar CircleTransformation para conseguir la imagen circular:

<ffimageloading:CachedImage 
     Aspect="AspectFit"
     Source="{Binding MyTrip.Picture}">
     <ffimageloading:CachedImage.Transformations>
          <fftransformations:CircleTransformation />
     </ffimageloading:CachedImage.Transformations>
</ffimageloading:CachedImage>

Sencillo, ¿verdad?.

Progreso circular con degradado

Sin duda alguna, la clave de la vista es la barra de progreso circular… y con degradado!. Por un lado, podemos irnos a por Custom Renderer y aprovechar código nativo en cada plataforma. Si queremos conseguir todo sólo con código compartido podríamos conseguir una barra circular de forma sencilla con imágenes y rotaciones, por ejemplo. Sin embargo, necesitamos diferentes recursos gráficos para adaptarnos a diferentes resoluciones. Por otro lado, el degradado añade complejidad.

¿La solución sencilla?

SkiaSharp. Engine gráfico 2D disponible para iOS, Android, UWP, macOS, Windows, etc.

Nos permite trabajar de forma sencilla desde co n figuras básicas a efectos y shaders más complejos.

En este caso, vamos a crear un control personalizado que herede de la clase SKCanvasView.

SKCanvasView es una vista Xamarin.Forms donde podremos dibujar utilizando SkiaSharp.

public class CircleCountdown : SKCanvasView
{

}

Vamos a crear varias BindableProperties para gestionar el comportamiento y apariencia de la barra de progreso:

public static readonly BindableProperty StrokeWidthProperty =
     BindableProperty.Create(nameof(StrokeWidth), typeof(float), typeof(CircleCountdown), 10f, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressProperty =
     BindableProperty.Create(nameof(Progress), typeof(float), typeof(CircleCountdown), 0f, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressStartColorProperty =
     BindableProperty.Create(nameof(ProgressStartColor), typeof(Color), typeof(CircleCountdown), Color.Blue, propertyChanged: OnPropertyChanged);

public static readonly BindableProperty ProgressEndColorProperty =
     BindableProperty.Create(nameof(ProgressEndColor), typeof(Color), typeof(CircleCountdown), Color.Red, propertyChanged: OnPropertyChanged);

public float StrokeWidth
{
     get { return (float)GetValue(StrokeWidthProperty); }
     set { SetValue(StrokeWidthProperty, value); }
}

public float Progress
{
     get { return (float)GetValue(ProgressProperty); }
     set { SetValue(ProgressProperty, value); }
}

public Color ProgressStartColor
{
     get { return (Color)GetValue(ProgressStartColorProperty); }
     set { SetValue(ProgressStartColorProperty, value); }
}

public Color ProgressEndColor
{
     get { return (Color)GetValue(ProgressEndColorProperty); }
     set { SetValue(ProgressEndColorProperty, value); }
}

Contamos con:

  • StrokeWidth: Permite controlar el grosor de la barra de progreso.
  • Progress: Determina el progreso. Acepta un valor numérico entre 0  y 1.
  • ProgressStartColor: ¿Recuerdas la necesidad de crear degradado?. Por ese motivo, vamos a contar con esta propiedad para gestionar el color de inicio del degradado.
  • ProgressEndColor: Color final del degradado.

Para dibujar el progreso circular, utilizaremos el método DrawPath para dibujar un Path al que le daremos la forma deseada utilizando el método AddArc.

using (SKPath path = new SKPath())
{
     path.AddArc(rect, StartAngle, angle);

     canvas.DrawPath(path, paint);
}

¿Y el degradado?.

En lugar de utilizar un SKColor (color en SkiaSharp) sólido, vamos a crear un shader. Utilizaremos el método CreateSweepGradient para crear el degradado:

var shader = SKShader.CreateSweepGradient(
     new SKPoint(size / 2, size / 2),
     new[]
     {
          ProgressStartColor.ToSKColor(),
          ProgressEndColor.ToSKColor(),
          ProgressStartColor.ToSKColor()
     },
     new[]
     {
          StartAngle / 360,
          (StartAngle + progressAngle + 1) / 360,
          (StartAngle + progressAngle + 2) / 360
});

Para utilizar el control, definimos el namespace:

xmlns:controls="clr-namespace:MyTripCountdown.Controls"

Y utilizamos el control:

<controls:CircleCountdown 
     VerticalOptions="FillAndExpand"
     HorizontalOptions="FillAndExpand"
     Progress="{Binding Progress}"
     ProgressStartColor="{StaticResource ProgressPinkColor}"
     ProgressEndColor="{StaticResource ProgressBlueColor}"
     StrokeWidth="10"/>

El progreso lo tenemos enlazado a una propiedad en la ViewModel donde se irá realizando el cálculo del progreso.

Botón con degradado

Llegamos al botón con degradado. Podríamos al igual que la barra de progreso circular, crearlo con SkiaSharp facilmente. Sin embargo, un botón cuenta con estados que tienen importancia. Habilitado, deshabilitado, pulsado, etc. Si queremos contar con un botón, debemos crear un Custom Renderer. No es complejo haciendo un Custom Renderer del botón y utilizando RippleDrawable con GradientDrawable en Android, y CAGradientLayer en iOS.

Sin embargo, la comunidad de Xamarin es fantástica. Contamos con un botón con degradado preparado para utilizar con Skor.UI. Tras añadir el paquete NuGet, añadimos el namespace:

xmlns:skor="clr-namespace:Skor.Controls;assembly=Skor.Controls"

Y utilizamos el control:

<skor:GradientButton 
     HeightRequest="60" 
     CornerRadius="96"
     StartColor="{StaticResource ProgressPinkColor}"
     EndColor="{StaticResource ProgressBlueColor}"
     Command="{Binding RestartCommand}"/>

De nuevo, muy sencillo esta parte de la interfaz, ¿verdad?.

Otros

Y no hay que olvidar que estamos haciendo un contador hacia atrás. ¿Formas de conseguirlo?. Tenemos diferentes opciones, directamente en Xamarin.Forms, tenemos el método StartTimer en la clase Device:

Device.StartTimer(TimeSpan.FromSeconds(seconds), () =>
{
     RemainTime = (EndDate - DateTime.Now);

     var ticked = RemainTime.TotalSeconds > 1;

     if (ticked)
     {
          Ticked?.Invoke();
     }
     else
     {
          Completed?.Invoke();
     }

     return ticked; 
});

¿Qué plugins o componentes se han utilizado?

Se ha utilizado:

  • FFImageLoading – Con el objetivo principal de gestionar la imágen circular. Esta librería también nos facilita importante funcionalidad relacionada con el cacheo de imágenes, etc. Aunque recuerda, en este ejemplo todas las imágenes son locales.
  • SKOR.UI – Nos facilita la creación de botones con degradados evitandonos crear un Custom Renderer.

Y llegamos a la parte final del artículo. Es un concepto de artículo que ya hemos realizado previamente con la creación de la interfaz de Netflix, por ejemplo. Es sumamente divertido preparar un diseño con cierto nivel de “reto” e intetar “desgranar” cada paso a realizar. Pero, ¿qué te parece este tipo de artículos?.

Cualquier duda o comentario es bienvenido en los comentarios!

El resultado

My Trip Countdown

Puedes encontrar el ejemplo en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Ejemplos de Backend Linux

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. El soporte de Linux (GTK) ahora está  disponible en Xamarin.Forms.

Para conocer y revisar las diferentes opciones disponibles, ¿algo mejor que contar con ejemplos?

Ejemplos Xamarin.Forms.Platform.GTK

El repositorio de ejemplos oficial de Xamarin.Forms es una fuente inmejorable de ejemplos cubriendo desde ejemplos básicos a uso de SkiaSharp, SQlite, gestos, efectos, etc. A continuación, puedes encontrar un repositorio donde encontrar la mayoría de ejemplos (y creciendo!) con versión GTK.

Puedes encontrar el repositorio en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Ejemplos de Backend WPF

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. El soporte de WPF ahora está  disponible en Xamarin.Forms.

Para conocer y revisar las diferentes opciones disponibles, ¿algo mejor que contar con ejemplos?

Ejemplos Xamarin.Forms.Platform.WPF

El repositorio de ejemplos oficial de Xamarin.Forms es una fuente inmejorable de ejemplos cubriendo desde ejemplos básicos a uso de SkiaSharp, SQlite, gestos, efectos, etc. A continuación, puedes encontrar un repositorio donde encontrar la mayoría de ejemplos (y creciendo!) con versión WPF.

Puedes encontrar el repositorio en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Novedades de la versión 3.2 pre1 (Preview)

Introducción

Recientemente se ha liberado la primera Preview de la versión 3.2 de Xamarin.Forms. Una nueva versión repleta de novedades que vamos a revisar en este artículo.

Las mejoras principales

A continuación, vamos a revisar las novedades principales (no son todas).

Control de la posición del caret

Comenzamos revisando una novedad sencilla pero muy interesante, controlar la posición del Caret. Ahora contamos con una nueva propiedad de tipo entero llamada CursorPosition.

<Entry 
     CursorPosition="4"
     Text="CursorPosition"/>

También tenemos un Platform Specific en el caso de iOS para poder controlar el color del cursor Entry<iOS>.CursorColor.

Commandable Span regions

Ya contábamos con la propiedad FormattedText para poder crear texto con formato utilizando Spans. Ahora nos llega la posibilidad de añadir Gesture Recognizers a cada elemento.

<Label>
     <Label.FormattedText>
          <FormattedString>
               <Span Text="{Binding Span1}" />
               <Span 
                    Text="{Binding Span2}" 
                    TextColor="Blue">
                    <Span.GestureRecognizers>
                         <TapGestureRecognizer Command="{Binding TapCommand}" />
                    </Span.GestureRecognizers>
               </Span>
               <Span Text="{Binding Span3}"/>
          </FormattedString>
     </Label.FormattedText>
</Label>

El resultado:

Commandable Spans

Abre muchas posibilidades a crear enlaces, abrir otras Apps (enviar correo, etc.).

Bordes redondeados en BoxView

Vamos con otra novedad bastante requerida, bordes redondeandos en el control BoxView.

<BoxView 
     CornerRadius="24, 0, 24, 24"
     BackgroundColor="Red"
     Color="Red"
     HeightRequest="100"
     Margin="12, 0"/>

Contamos con la propiedad CornerRadius que permite establecer el valor del borde de cada esquina.

Bordes redondeados en BoxView

Paddings en botones

Añadida opción para controlar el Padding en botones:

<Button 
     Text="With Padding"
     Padding="6, 12, 48, 0"/>

La nueva propiedad Padding es de tipo Thickness.

Page TitleView

Estoy seguro que la siguiente novedad va a ahorrar más de un Custom Renderer. Estamos ante una propiedad adjunta llamada TitleView, como TitleIcon de la NavigationPage.

<NavigationPage.TitleView>
     <StackLayout>
          <Label 
               Text="TitleView"
               TextColor="White"
               FontSize="28"/>
          <Label 
               Text="I can be a subtitle"
               TextColor="White"/>
          <SearchBar 
               Placeholder="This is a SearchBar"
               PlaceholderColor="Black"
               BackgroundColor="White"
               HorizontalOptions="FillAndExpand"/>
     </StackLayout>
</NavigationPage.TitleView>

Podemos establecer el valor de TitleView en cada Page para personalinar el valor.

El resultado:

TitleView

Es posible establecer también el valor desde C#:

NavigationPage.SetTitleView(this, MyTitleView());

Nuevas propiedades Placeholder y PlaceholderColor en el Editor

Añadidas propiedades Placeholder y PlaceholderTextColor:

<Editor 
     Placeholder="This is the Placeholder"
     PlaceholderColor="Orange"/>

El resultado:

Placeholder en Editor

OnPlatform/OnIdiom markup extensions

Llega nueva extensión de marcado para simplificar la gestión de valores utilizados con OnPlatform u OnIdiom desde XAML.

¿El cambio?

Podemos pasar de:

<Label Text="This is nice!">
     <Label.FontSize>
          <OnPlatform x:TypeArguments="x:Double">
               <On Platform="iOS" Value="20" />
               <On Platform="Android" Value="24" />
               <On Platform="UWP" Value="28" />
          </OnPlatform>
     </Label.FontSize>
</Label>

A:

<Label 
     Text="This is nice!"
     FontSize="{OnPlatform Android=24, iOS=20, UWP=28}" />

SwipeGestureRecognizer

Dentro del conjunto de gestos disponibles en Xamarin.Forms, había uno que se echaba de menos, Swipe.

var swipe = new SwipeGestureRecognizer
{
     Direction = SwipeDirection.Left
};

swipe.Swiped += (sender, args) => 
{

};

Podemos detectar el gesto Swipe a izquierda, derecha, arriba y abajo.

Swipe!

Puedes encontrar el ejemplo en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Principales novedades de la versión 3.1

Introducción

Contamos con nueva versión de Xamarin.Forms, la 3.1. Una versión que tiene una variedad de novedades centradas en reducir la cantidad de controles personalizados y código específico en nuestras aplicaciones. En este artículo, vamos a hacer un rápido repaso sobre las novedades principales.

Novedades

Entre el conjunto de novedades principales encontramos:

Pestañas en la parte inferior

Google añadió BottomNavigationView en Android Support Library 25, y gracias a ello, ha sido posible añadirlo en Xamarin.Forms de forma sencilla utilizando Platform Specific.

<TabbedPage
     xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
     BarBackgroundColor="LightBlue"
     android:TabbedPage.ToolbarPlacement="Bottom"
     android:TabbedPage.BarItemColor="Gray"
     android:TabbedPage.BarSelectedItemColor="Black"
     Title="Bottom Tabs">
     <TabbedPage.Children>
          <ContentPage 
               Title="First"
               Icon="first">
               <Grid />
          </ContentPage>
          <ContentPage
               Title="Second"
               Icon="second">
               <Grid />
          </ContentPage>
          <ContentPage
               Title="Third"
               Icon="third">
               <Grid />
          </ContentPage>
     </TabbedPage.Children>
</TabbedPage>

Se puede establecer la posición de las pestañas con la propiedad ToolbarPlacement. También se cuentan con propiedades destinadas a personalizar el estilo de las pestañas:

  • BarItemColor: Color del icono y texto de cada pestaña.
  • BarSelectedItemColor: Color del icono y texto de la pestaña seleccionada.
Pestañas en la parte inferior

Auto-Resize Editor

Otra pequeña novedad bastante requerida. Según se va escribiendo en un control de tipo Editor, el control va creciendo automáticamente. Lo podemos conseguir gracias a la propiedad AutoSize que permite los siguientes valores:

  • Disabled: Valor por defecto. El control no crecerá.
  • TextChanges: El control crece automáticamente para permitir mostrar el contenido.
<Editor
     Text="AutoResize"
     AutoSize="TextChanges"/>

El resultado:

Autoresize Editor

Return key para Entry

Con la nueva propiedad ReturnType del Entry podemos modificar la apariencia del botón para completar la acción en el teclado virtual.

<Entry 
     ReturnType="Send"/>

Los valores posibles para ReturnType son:

  • Default
  • Done
  • Go
  • Next
  • Search
  • Send

El resultado:

Return Key

AutoCapitalization keyboard para el Entry

A veces en ciertos formularios no deseamos que el teclado automáticamente establezca la primera letra en mayúsculas. Por ejemplo, al introducir un correo electrónico. Ahora podemos controlar el comportamiento del teclado con KeyboardFlags:

<Entry 
     Placeholder="Insert text">
     <Entry.Keyboard>
          <Keyboard x:FactoryMethod="Create">
               <x:Arguments>
                    <KeyboardFlags>CapitalizeNone</KeyboardFlags>
               </x:Arguments>
          </Keyboard>
     </Entry.Keyboard>
</Entry>

Puedes consultar los valores posibles de KeyboardFlags en el siguiente enlace.

Auto Capitalization

Opciones de scrollbar Vertical y Horizontal

Se han añadido propiedades para controlar la visibilidad de las barras de scroll del ScrollView, tanto horizontal como vertical.

<ScrollView
     VerticalScrollBarVisibility="Always" 
     HorizontalScrollBarVisibility="Never">
</ScrollView>

Bindable Span

Otra novedad bastante interesante. Ahora podemos usar enlace a datos en Spans además de poder personalizar el color de cada Span:

<Label>
     <Label.FormattedText>
          <FormattedString>
               <FormattedString.Spans>
                    <Span Text="Test" />
                   <Span Text="{Binding Info}"
                         TextColor="Red" />
               </FormattedString.Spans>
          </FormattedString>
     </Label.FormattedText>
</Label>

Invocar JavaScript desde WebView

Ahora podemos invocar de forma sencilla código JavaScript desde el control WebView, utilizando una cadena con la posibilidad de obtener el valor de retorno.

string result = await EvaluateJsWebView.EvaluateJavaScriptAsync(
"var test = function(){ return 'This string came from Javascript'; }; test();");

Lo conseguimos con el nuevo método EvaluateJavaScriptAsync que evalua el código pasado como parámetro y retorno un valor siempre en formato de cadena.

Otros

Hay muchos más pequeños cambios como:

Puedes encontrar el ejemplo en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Addins y extensiones para Visual Studio macOS y Windows

Introducción

Visual Studio para macOS y Windows cuentan con modelos de extensión diferentes, modelos para crear interfaces de usuario diferentes y habitualmente trasladar un addin de macOS a una extension en Windows o viceversa, es costoso. ¿Podemos simplificar el proceso?, ¿podemos lograr compartir más elementos?.

En este artículo, vamos a ver como crear addins para Visual Studio macOS así como extensiones para Visual Studio Windows utilizando Xamarin.Forms para lograr compartir la UI.

Utilizaremos Measurer4000, herramienta diseñada para obtener métricas de código compartido en proyectos Xamarin. Nuestro objetivo será añadir la herramienta dentro de Visual Studio.

Add-ins para Visual Studio macOS

La arquitectura de Visual Studio para macOS es extensible. La forma de extender se basa en rutas de extensión que permiten que terceros extiendan el comportamiento. Por ejemplo: para extender la zona de edición de código, se expone /MonoDevelop/SourceEditor2/ContextMenu/Editor permitiendo añadir nuevos comandos en el menu contextual al hacer clic derecho.

AddIn Maker es un proyecto Open Source creado por Mikayla Hutchinson que permite el desarrollo y la depuración de add-ins para Visual Studio macOS desde Visual Studio macOS.

Para la gestión de la interfaz de usuario se utiliza GTK# o XWT.

Extensiones para Visual Studio Windows

En Windows tenemos la posibilidad de extender practicamente cualquier parte: menús, barras de herramientas, ventanas, etc.

Para crear extensiones se deben instalar las opciones de Desarrollo de extensiones de Visual Studio.

Extensiones en Windows

Para crear extensiones contamos con una plantilla de proyecto VSIX vacía que puede usar junto con las nuevas plantillas de elemento que crean extensiones de editor, ventanas de herramientas y comandos de menú.

Para la definición de la UI, utilizamos WPF.

Xamarin.Forms

Con la llegada de Xamarin.Forms 3.0 encontramos soporte a Linux gracias a un nuevo backend basado en GTK# además de un backend WPF.

Por lo que si usamos GTK# y WPF, ¿podríamos usar XAML y Xamarin.Forms para definir la UI de add-ins para Visual Studio tanto para macOS como para Windows?, ¿podría ser compartida?.

El proyecto en macOS

Comenzamos creando un proyecto de tipo IDE Extension:

IDE Extension

Este proyecto ya cuenta con la referencia a MonoDevelop.Addins. Continuamos añadiendo el paquete NuGet de Xamarin.Forms y Xamarin.Forms.Platform.GTK.

Para trabajar con Xamarin.Forms, debemos realizar la inicialización. La mejor opción para realizar esta tarea es realizarla nada más se abra el IDE. Para ello, vamos a crear un comando:

public class InitXamarinFormsCommand : CommandHandler
{
     protected override void Run()
     {
          Forms.Init();
     }
}

Y modificaremos el archivo Manifiest.addin.xml para añadir nuestro comando como punto de extensión en el arranque del IDE.

<Extension path="/MonoDevelop/Ide/StartupHandlers">
     <Class class="Measurer4000.Addin.Mac.Commands.InitXamarinFormsCommand"/>
</Extension>

Llega la hora de extender.

Añadimos otro punto de extensión:

<Extension path = "/MonoDevelop/Ide/MainMenu/Tools">
     <CommandItem id="Measurer4000.Addin.Mac.Commands.MeasurerCommand"/>
</Extension>

Fíjate que en esta ocasión, añadiremos un nuevo comando en el menu principal, opción herramientas.

Veamos la definición del comando:

public class MeasurerCommand : CommandHandler
{
     protected override void Update(CommandInfo info)
     {
          info.Visible = true;

          var projects = IdeApp.Workspace.GetAllProjects();

          if (projects.Any())
               info.Enabled = true;
          else
               info.Enabled = false;
     }

     protected override void Run()
     {
          new MeasurerWindow().Show();
     }
}

Comenzamos utilizando IdeApp. La clase estática MonoDevelop.Ide.IdeApp es su punto de entrada de extension de Visual Studio. Expone Workbench, Workspace, múltiples servicios  así como métodos relacionados con el ciclo de vida del IDE.

Habitualmente, la clase IdeApp se utiliza para acceder a Workbench o a Workspace. IdeApp.Wortkspace se usa para acceder al estado general del Ide durante una sesión de usuario. Podemos abrir nuevos proyectos y documentos, detectar archivos abiertos y mucho más.

En nuestro caso, lo utilizamos para detectar si el usuario tiene abierto al menos un proyecto.

En caso de abrir un proyecto, habilitamos la opción.

Al ejecutar el comando lanzamos una ventana llamada MeasurerWindow.

public class MeasurerWindow: Gtk.Window
{
     public MeasurerWindow()
     : base(Gtk.WindowType.Toplevel)
     {
          Title = "Measurer4000";
          WindowPosition = Gtk.WindowPosition.Center;

          var page = new MeasurerView();

          Add(page.CreateContainer());

          SetSizeRequest(800, 480);
     }
}

Es una ventana realizada con Gtk cuyo contenido utilizará una ContentPage de Xamarin.Forms. La clave para utilizar Xamarin.Forms es embeber el contenido. Tenemos disponible el método de extensión CreateContainer de Xamarin.Forms en el backend GTK para obtener la ContentPage como contenido nativo.

El proyecto en Windows

Comenzamos el proyecto en Windows añadiendo un comando.

Nuevo comando

Los menús y las barras de herramientas son la forma en que los usuarios acceden a los comandos en el VSPackage. Los comandos son funciones que realizan tareas, como mostrar una ventana o crear un nuevo archivo. Los menús y barras de herramientas son formas gráficas convenientes para presentar los comandos a los usuarios.

En este comando realizamos la inicializacion de Xamarin.Forms:

public static void Initialize(Package package)
{
     Instance = new MeasurerCommand(package);

     Forms.Init();
}

Al ejecutar el comando:

private void MenuItemCallback(object sender, EventArgs e)
{
     var measurerWindow = new MeasurerWindow();

     var measurerContent = new MeasurerView().ToFrameworkElement();
     measurerWindow.Content = measurerContent;

     measurerWindow.Show();
}

Lanzamos una ventana llamada MeasurerWindow (al igual que ya hacíamos en macOS), ventana WPF cuyo contenido es una ContentPage de Xamarin.Forms. La clave para utilizar Xamarin.Forms es de nuevo embeber el contenido. Tenemos disponible el método de extensión ToFrameworkElement de Xamarin.Forms en el backend WPF para obtener la ContentPage como contenido nativo.

La ventana:

<Window 
     x:Class="Measurer4000.Addin.Windows.Windows.MeasurerWindow"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     mc:Ignorable="d"
     WindowStartupLocation="CenterScreen"
     Title="Measurer 4000">
     <Grid>

     </Grid>
</Window>

UI XAML compartida

Tanto en macOS como en Windows utilizamos una ContentPage para definir la interfaz de usuario. Hablamos de una interfaz definida en XAML que puede ser compartida en macOS y Windows, donde en cada plataforma, se utilizará un backend diferente para renderizar la UI con GTK# en el caso de macOS y con WPF en el caso de Windows.

<Grid>
     <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition Height="*" />
          <RowDefinition Height="Auto" />
     </Grid.RowDefinitions>
     <Grid Grid.Row="0"
          Margin="24">
          <Grid.ColumnDefinitions>
               <ColumnDefinition />
               <ColumnDefinition />
               <ColumnDefinition />
          </Grid.ColumnDefinitions>
          <StackLayout Grid.Column="0">
          <StackLayout Orientation="Vertical"
               HorizontalOptions="Center"
               Margin="0, 24, 0, 0">
               <Label Text="iOS Specific Code" />
               <Label Text="{Binding Stats.iOSSpecificCode}"
                    FontSize="30" />
          </StackLayout>
          <StackLayout Orientation="Vertical"
               HorizontalOptions="Center"
               Margin="0, 24, 0, 0">
               <Label Text="Share code in iOS" />
               <Label Text="{Binding Stats.ShareCodeIniOS}"
                    FontSize="30" />
          </StackLayout>
     </StackLayout>
     ...

En la interfaz utilizaremos algunos Labels donde msotrar información con cantidad de líneas así como porcentajes de código compartido y código específico. Por otro lado, utilizamos Oxyplot para mostrar gráficos de tipo tarta.

public MeasurerView()
{
     InitializeComponent();

     BindingContext = new CustomMainViewModel();
}

Toda la lógica del addin, modelos, ViewModels, etc. es la ya existente en la herramienta. Utilizaremos MVVM de exactamente la misma forma utilizada para crear Apps, en nuestro addin. En la ViewModel es necesario pasar la ruta de la solución para que pueda analizar y obtener toda la información.

En macOS tenemos en IdeApp.Workspace lo necesario para obtener la información necesaria de la solución con la que trabaja el usuario. Podemos obtener el listado de soluciones con el metodo GetAllSolutions.

var solutionPath = Path.Combine(Directory.GetCurrentDirectory(), solution.FileName);
_viewModel.MeasureSolutionByPath(solutionPath);

En el caso de Windows, obtenemos la instancia del objeto DTE (Development Tools Environment). DTE representa el entorno de desarrollo integrado (IDE) de Visual Studio y es el objeto de máximo nivel en la jerarquía. Nos otorga acceso a una gran variedad de propiedad y métodos para obtener información de la solución, propiedades, ventanas, etc.

var solution = ((DTE)ServiceProvider.GlobalProvider.GetService(typeof(DTE))).Solution;
var solutionPath = solution.FullName;
_viewModel.MeasureSolutionByPath(solutionPath);

Accedemos a la solución, y extraemos la ruta a la misma.

El resultado en macOS:

Addin en macOS

Y en Windows:

Addin en Windows

¿Qué te parece?. Quizás en un futuro sea una opción a la hora de extender Visual Studio. Ahora que tenemos el IDE en más de un sistema operativo, es hora de pensar en contar con una opción para crear extensiones que compartan la mayor cantidad de código posible.

Más información

Renovado como Microsoft MVP 2018!

Microsoft MVP 2018

Abres el correo y…

Hola Javier Suarez,

Una vez más, nos complace entregarle el Premio Microsoft Most Valuable Professional (MVP)…

Van cinco y tras otro año, volver a recibirlo hace la misma e incluso más ilusión!. Un gran honor formar parte de un grupo de desarrolladores tan talentosos y apasionados además de grandes personas.

Microsoft MVP

Agradecimientos

Llegados a este punto, siempre apetece agradecer a muchas partes que han ayudado en el camino. Comienzo por vosotros, si, si hablo de ti querido lector. Gracias por leer estos artículos técnicos y ayudarme a aprender intercambiando opiniones día a día, gracias a mis compañeros MVPs, a los grupos de usuario SVQXDG y CartujaDotNet donde colaboro y me permiten aprender más y más cada día con fantásticos compañeros y a Cristina González, MVP lead, por su increíble labor siempre pendiente de todos nosotros además de a los chicos de Microsoft España. Podría continuar nombrando a muchos amigos y compañeros, pero me temo que me extendería en exceso y al final y al cabo sabéis quienes sois.

Por último, en esta ocasión me gustaría dar todo mi apoyo a amigos que dejan el programa. Estoy seguro que estarán de vuelta pronto además de por supuesto, seguir aportando a toda la comunidad.

Y ahora que?

Ahora, a continuar aprendiendo y disfrutando compartiendo todo lo posible en blogs, comunidades técnicas y proyectos Open Source con más ganas y más ilusión si cabe.

Felicidades

También me gustaría felicitar a todos los MVPs veteranos que han sido renovados asi como aquellos que entran a formar parte desde este momento. Enhorabuena!

[Xamarin.Forms] Bottom TabbedPage para Android

Xamarin.Forms 3.1

Con la llegada de Xamarin.Forms 3.1 llegan una gran cantidad de mejoras y correcciones (es genial ver que muchas vienen de la comunidad). Entre el conjunto de novedades tenemos la posibilidad de crear TabbedPage en la parte inferior en Android.

En este artículo, vamos a ver como crear una Bottom TabbedPage.

Bottom TabbedPage

Podemos crear una TabbedPage con posición en la parte inferior de forma muy sencilla en Android utilizando platform specific:

On<Xamarin.Forms.PlatformConfiguration.Android>().SetToolbarPlacement(ToolbarPlacement.Bottom);

El resultado:

Bottom TabbedPage

Tienes un ejemplo disponible en GitHub:

Ver GitHub

Más información

[Xamarin.Forms] Utilizando LiteDB

Database-WFIntroducción

El trabajo con datos en dispositivos móviles se ha convertido ya en algo común y habitual en el desarrollo de aplicaciones. Existe una gran variedad de tipos de datos y formas de almacenamiento:

  • Archivos de texto. Texto plano o html cacheado en el espacio de almacenamiento aislado de la aplicación.
  • Imágenes. En el espacio de almacenamiento aislado de la aplicación o almacenadas en directorios conocidos del sistema.
  • Archivos serializados. Archivos XML o Json con objetos serializados.
  • Bases de datos. Cuando se requieren datos estructurados, obtener información más compleja con consultas avanzadas entre otro tipo de necesidades, la posibilidad de las bases de datos es la elección idónea.

En este artículo, vamos a conocer LiteDB y como utilizarlo con una aplicación Xamarin.Forms.

Recuerda

El mismo ejemplo que vemos en este artículo lo hemos visto previamente con:

Introducción a LiteDB

LiteDB es una base de da motor de base de datos Open Source  distribuido en una pequeña librería escrita en C# y compatible con .NET y .NET Standard. Inspirada en MongoDB almacena documentos BSON (Binary JSON).

Al ser compatible con .NET Standard 2.0, podemos utilizarla con Xamarin.iOS, Xamarin.Android y UWP con aplicaciones Xamarin.Forms.

Preparando el entorno

Comenzamos creando una aplicación Xamarin.Forms utilizando una librería .NET Standard:

Xamarin.Forms con librería .NET Standard

Tras crear la aplicación, añadimos las carpetas básicas para aplicar el patrón MVVM además del paquete NuGet de Autofac para la gestión del contenedor de dependencias.

Estructura del proyecto

Con el proyecto y estructura base creada, vamos a añadir LitrDB al proyecto. LiteDB esta disponible en NuGet. Vamos a añadir en cada proyecto de la solución la última versión disponible del paquete utilizando NuGet. El paquete a utilizar es LiteDB, implementación Open Source compatible con librerías .NET Standard.

LiteDB

Tras añadir la referencia vamos a crear una interfaz que defina como obtener la conexión con la base de datos y abstraer la funcionalidad específica de cada plataforma. Trabajando con LiteDB, el único trabajo específico a implementar en cada plataforma es determinar la ruta a la base de datos y establecer la conexión.

public interface IPathService
{
     string GetDatabasePath();
}

En Android, la implementación de IPathService nos permite establecer la conexión con la base de datos.

public class PathService : IPathService
{
     public string GetDatabasePath()
     {
          var path = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), AppSettings.DatabaseName);
          if (!File.Exists(path))
          {
               File.Create(path).Dispose();
          }
          return path;
     }
}

NOTA: Utilizamos el atributo assembly:Dependency para poder realizar la resolución de la implementación con DependencyService.

En iOS, la implementación de IPathService nos permite establecer la conexión con la base de datos. El archivo de la base de datos lo situamos dentro de la carpeta Library dentro del espacio de almacenamiento de la aplicación.

public class PathService : IPathService
{
     public string GetDatabasePath()
     {
          string docFolder = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
          string libFolder = Path.Combine(docFolder, "..", "Library", "Databases");
          if (!Directory.Exists(libFolder))
          {
               Directory.CreateDirectory(libFolder);
          }
          return Path.Combine(libFolder, AppSettings.DatabaseName);
     }
}

Y por último en UWP:

public class PathService : IPathService
{
     public string GetDatabasePath()
     {
          return Path.Combine(ApplicationData.Current.LocalFolder.Path, AppSettings.DatabaseName);
     }
}

Todo listo para comenzar!

La definición de modelos

En nuestra aplicación, trabajaremos con elementos del listado ToDo, una única entidad sencilla.

public class TodoItem
{
     public int Id { get; set; }
     public string Name { get; set; }
     public string Notes { get; set; }
     public bool Done { get; set; }
}

La interfaz de usuario

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

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

<ListView ItemsSource="{Binding Items}"
     SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
     <ListView.ItemTemplate>
          <DataTemplate>
               <ViewCell>
                    <ViewCell.View>
                         <StackLayout Padding="20,0,20,0"
                              Orientation="Horizontal"
                              HorizontalOptions="FillAndExpand">
                              <Label Text="{Binding Name}"
                                   VerticalTextAlignment="Center"
                                   HorizontalOptions="StartAndExpand" />
                              <Image HorizontalOptions="End"
                                   IsVisible="{Binding Done}">
                                   <Image.Source>
                                        <OnPlatform x:TypeArguments="ImageSource">
                                             <On Platform="Android, iOS"
                                                  Value="check" />
                                             <On Platform="UWP"
                                                  Value="Assets/check.png" />
                                        </OnPlatform>
                                   </Image.Source>
                              </Image>
                         </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">
                     <On Platform="Android, iOS"
                         Value="plus" />
                     <On Platform="UWP"
                         Value="Assets/plus.png" />
                </OnPlatform>
          </ToolbarItem.Icon>
     </ToolbarItem>
</ContentPage.ToolbarItems>

Añadimos un ToolbarItem que permitirá añadir elementos.

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:

Vista principal
Vista principal

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

BindingContext = App.Locator.TodoListViewModel;

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

private ObservableCollection<TodoItem> _items;
private TodoItem _selectedItem;

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

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

private ICommand _addCommand;

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

private void AddCommandExecute()
{
}

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

var todoItem = new TodoItem();
_navigationService.NavigateTo<TodoItemViewModel>(todoItem);

Si creamos un nuevo elemento pasaremos como parámetro una nueva entidad de TodoItem, 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="Name" />
     <Entry Text="{Binding Item.Name}" />
     <Label Text="Notes" />
     <Entry Text="{Binding Item.Notes}" />
     <Label Text="Done" />
     <Switch x:Name="DoneEntry"
          IsToggled="{Binding Item.Done}" />
     <Button Text="Save"
          Command="{Binding SaveCommand}" />
     <Button Text="Delete"
          Command="{Binding DeleteCommand}" />
     <Button Text="Cancel"
          Command="{Binding CancelCommand}" />
</StackLayout>

Añadimos cajas de texto para poder editar toda la información de una tarea además de botones para poder guardar, borrar o cancelar y navegar atrás.

El resultado:

Detalle
Detalle

Para enlazar la información de un elemento seleccionado, debemos capturar la información enviada en la navegación. Creamos una propiedad pública para enlazar con la UI de la tarea:

private TodoItem _item;

public TodoItem Item
{
     get { return _item; }
     set
     {
          _item = value;
          OnPropertyChanged();
     }
}

¿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)
{
     if (navigationContext is TodoItem todoItem)
     {
          Item = todoItem;
     }

     base.OnAppearing(navigationContext);
}

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

Trabajando con LiteDB

Para trabajar con la base de datos utilizaremos DependencyService para obtener la implementación de IPathService y obtener una conexión.

var db = new LiteDatabase(DependencyService.Get<IPathService>().GetDatabasePath());

Para almacenar nuestras tareas, comenzamos creando la colección necesaria en la base de datos.

_collection = db.GetCollection<TodoItem>();

Si la colección no existe en la base de datos, es creada.

Cada elemento guardado en LiteDB cuenta con una propiedad Id utilizado para identificarlo de manera única. Debeemos indicarle a LiteDB qué propiedad de nuestro objeto actuará como identificador. Podemos hacerlo usando atributos o un mapeador. Aquí utilizaremos Mappers.

var mapper = BsonMapper.Global;

mapper.Entity<TodoItem>()
.Id(x => x.Id);

Para obtener el listado de elementos de una colección utilizaremos el método FindAll:

var all = _collection.FindAll();

A la hora de insertar, verificamos si estamos ante un registro existente o no, para realizar el registro de un nuevo elemento o actualizar uno existente con los métodos Insert o Update respectivamente.

var existingTodoItem = _collection.FindById(item.Id);

if (existingTodoItem == null)
_collection.Insert(item);
else
_collection.Update(item);

Eliminar es una acción sencilla realizada con el método Delete.

_collection.Delete(i => i.Id.Equals(item.Id));

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

Ver GitHub

Recordad que podéis dejar cualquier comentario, sugerencia o duda en los comentarios.

Más información

[Quedada Informal] CartujaDotNet & SVQXDG

Quedada múltiple

Desde CartujaDotNet, grupo de usuarios .NET de Sevilla y SVQXDG, grupo de desarrolladores Xamarin de Sevilla, vamos a realizar una quedada informal (la última del año!) para charlar abiertamente sobre tecnologías Microsoft, Xamarin, herramientas utilizadas, intercambiar impresiones, etc. Además, se analizarán las próximas charlas ya planteadas y los eventos confirmados entre otros temas de interés. Al ser quedada de dos grupos diferentes creemos que es una gran oportunidad para conocer, intercambiar e interactuar entre ambos permitiendo a miembros de cada uno conocer a los del otro y tratar otros aspectos.

No hace falta confirmar asistencia, y por supuesto será gratuito.

¿Te apuntas?

A continuación tienes disponible la fecha, hora y lugar:

  • Día: 05 de Julio (Jueves)
  • Horario:  19:00h
  • Lugar: En la Terraza del McDonald’s de Santa Justa

Más información