[Xamarin.Forms] Uso de Mapas básico con MVVM

mapsIntroducción

En el desarrollo de aplicaciones móviles contamos con una serie de controles de peso, altamente utilizados y que definen las características básicas de muchas aplicaciones. Hablamos de controles como listados, carruseles o mapas.

En Xamarin.Forms se pueden utilizar mapas utilizando las APIs nativas de cada plataforma. El uso básico se ve altamente simplifcado utilizando un paquete NuGet, Xamarin.Forms.Maps, que requiere cierta configuración inicial.

Trabajando con mapas, el inicio

Para trabajar con mapas en Xamarin.Forms debemos de comenzar añadiendo un paquete NuGet en cada proyecto de la solución, es decir, tanto en la librería portable como en cada proyecto nativo.

NuGet
NuGet

Tras añadir el paquete NuGet se deben de realizar tareas de inicialización.

En el caso de iOS se debe añadir:

Xamarin.FormsMaps.Init();

En el delegado principal de la aplicación tras inicializar Xamarin.Forms en el método FinishedLaunching.

En el caso de Android:

Xamarin.FormsMaps.Init(this, bundle);

Se realiza la inicialización de los mapas en la actividad principal utilizando el método OnCreate.

Por último, en el caso de Windows, la inicialización se realiza en el constructor de la página principal, MainPage:

Xamarin.FormsMaps.Init("INSERT_AUTHENTICATION_TOKEN_HERE");

Además de realizar el proceso de inicialización, los mapas requieren de ciertas capacidades además de requerir un Api Key correspondiente a los mapas nativos utilizados en cada plataforma.

En iOS, si se utiliza iOS 8 o superior, es necesario añadir dos claves al Info.plist.

<key>NSLocationAlwaysUsageDescription</key>
    <string>Can we use your location</string>
<key>NSLocationWhenInUseUsageDescription</key>
    <string>We are using your location</string>

En Android se utiliza la API de Google Maps que requiere un Api Key. A nivel de aplicación en el arhivo de manifiesto Android es necesario añadir:

<meta-data android:name="com.google.android.maps.v2.API_KEY"
            android:value="INSERT_API_KEY" />

Además, es necesario añadir los siguientes permisos:

  • AccessCoarseLocation
  • AccessFineLocation
  • AccessLocationExtraCommands
  • AccessMockLocation
  • AccessNetworkState
  • AccessWifiState
  • Internet

Se pueden añadir desde Opciones > Build > Android Application.

Para trabajar con Windows, como ya vimos en el proceso de inicialización, se necesita generar un token de autorización para poder autenticar con Bing Maps.

Uso básico de mapas

A la hora de trabajar con mapas en Xamarin.Forms haremos uso del control Map. El control se puede inicializar y utilizar desde C#:

var map = new Map(
            MapSpan.FromCenterAndRadius(
                   new Position(37,-122), Distance.FromMiles(0.3))) {
                   IsShowingUser = true,
                   HeightRequest = 100,
                   WidthRequest = 960,
                   VerticalOptions = LayoutOptions.FillAndExpand
            };

Y desde XAML:

  <maps:Map 
       x:Name="MyMap"
       WidthRequest="320" 
       HeightRequest="200" />

Donde el namespace XAML es:

xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"

Las propiedades fundamentales del mapa son:

  • MapType: Permite establecer el tipo de mapa entre vista satélite, calle e híbrida.
  • Pins: Conjunto de pushpins del mapa.

Para añadir pushpins al mapa basta con rellenar la colección Pins con objetos de tipo Pin.

En cuanto a los métodos fundamentales tenemos:

  • MoveToRegion: Permite establecer y modificar la posición y nivel de zoom aplicado al mapa.
MyMap.MoveToRegion(
    MapSpan.FromCenterAndRadius(
      new Position(37,-122), Distance.FromMiles(1)));

Utilizando MVVM

Hasta este punto tenemos el mapa funcionando y contamos con todos los conceptos básicos necesarios para trabajar con mapas. Sin embargo, rellenar la colección de pushpins utilizando la propiedad Pins o posiciona el mapa utilizando el método MoveToRegion en el código asociado, no es lo que habitualmente realizamos.

¿Cómo utilizamos todo utilizando MVVM?

Pongámonos en situación. Habitualmente tendremos cierto contexto. Imagina que tenemos una aplicación de Taxis donde por supuesto, tendremos clientes. Cada cliente lo representaremos utilizando una entidad, dentro de la carpeta Models:

    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Title { get; set; }
        public string Phone { get; set; }
        public string Address { get; set; }
        public string Description { get; set; }
        public double Longitude { get; set; }
        public double Latitude { get; set; }
        public DateTime? FullyAttendedTime { get; set; }
    }

La lógica necesaria para añadir pushpins al mapa es sencilla utilizando la propiedad Pins del mapa. También podemos gestionar la posición central del mapa y el nivel de zoom utilizando el método MoveToRegion:

private void AddPins()
{
     foreach (var customer in DataRepository.LoadCustomerData())
     {
                var pin = new Pin
                {
                    Type = PinType.Place,
                    Position = new Position(customer.Latitude, customer.Longitude),
                    Label = customer.Name,
                    Address = customer.Address
                };

                MyMap.Pins.Add(pin);
     }
}

private void PositionMap()
{
     MyMap.MoveToRegion(
          MapSpan.FromCenterAndRadius(
               new Position(GlobalSetting.UserLatitude, GlobalSetting.UserLongitude),
               Distance.FromMiles(1)));
}

Vamos a aprovechar toda la versatilidad y potencia que nos brindan los Behaviors en Xamarin.Forms.

El concepto de Behavior es algo muy sencillo. Un Behavior espera por “algo” para hacer “algo”. Concretamos más. Un Behavior espera por “algo”. Puede ser un evento que se lanza, el cambio de una propiedad o cualquier otra acción personalizada que deseamos monitorear. Una vez que ese “algo” se desencadena, el Behavior puede hacer acciones muy variadas, desde cambiar el valor de una propiedad, lanzar un evento, hacer verificaciones o validaciones, etc.

Los Behaviors nos permiten encapsular lógica que se puede adjuntar a un componente específico. Generalmente ayudan a personalizar o completar ciertos componentes e incluso en muchas ocasiones son un a ayuda fundamental para mantener una estructura idónea al implementar patrones como MVVM.

Comenzamos creando la clase MapBehavior que debe de heredar de BindableBehavior<Map>:

public class MapBehavior : BindableBehavior<Map>
{

}

Para permitir tener acceso a la colección de pushpins vamos a crear una BindableProperty de tipo IEnumerable<Customer>:

public static readonly BindableProperty ItemsSourceProperty =
     BindableProperty.CreateAttached("ItemsSource", typeof(IEnumerable<Customer>), typeof(MapBehavior), 
          default(IEnumerable<Customer>), BindingMode.Default, null, OnItemsSourceChanged);

public IEnumerable<Customer> ItemsSource
{
     get { return (IEnumerable<Customer>)GetValue(ItemsSourceProperty); }
     set { SetValue(ItemsSourceProperty, value); }
}

Al recibir la información, añadiremos los pushpins al mapa y centraremos la posición deseada:

private static void OnItemsSourceChanged(BindableObject view, object oldValue, object newValue)
{
     var mapBehavior = view as MapBehavior;

     if (mapBehavior != null)
     {
          mapBehavior.AddPins();
          mapBehavior.PositionMap();
     }
}

Sencillo, ¿cierto?. Pues ahora llega el momento de utilizar el Behavior. Añadimos en la página el namespace XAML necesario:

xmlns:behaviors="clr-namespace:MyTaxiCompany01.Behaviors;assembly=MyTaxiCompany01"

Utilizamos el Behavior:

<AbsoluteLayout>
    <maps:Map
      AbsoluteLayout.LayoutFlags="All"
      AbsoluteLayout.LayoutBounds="0, 0, 1.0, 1.0">
      <maps:Map.Behaviors>
        <behaviors:MapBehavior 
             ItemsSource="{Binding Customers}" />
      </maps:Map.Behaviors>
    </maps:Map>
</AbsoluteLayout>

El resultado:

La aplicación de ejemplo
La aplicación de ejemplo

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

Ver GitHub

Hasta este punto hemos aprendido como utilizar mapas en Xamarin.Forms de forma básica. Sin embargo, en las aplicaciones que hacen un uso intensivo de mapas vemos características como:

  • Pushpins totalmente personalizados.
  • Pushpins dinámicos (se añaden, se quitan e incluso se desplazan).
  • Mostrar un  diálogo al pulsar un puhspin.
  • Traza de rutas.
  • Dibujo de figuras poligonales.

¿Cómo hacemos estas opciones en Xamarin.Forms?. En próximos artículos iremos profundizando en el uso de mapas viendo como realizar cada uno de los puntos anteriores.

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

Más información

[Xamarin.Forms] Probando UI Sleutch

uisleuthIntroducción

Xamarin.Forms es un toolkit que crea una abstracción sobre la interfaz de usuario de Android, iOS y Windows Phone permitiendo desarrollarla una única vez con código C# o Extensible Application Markup Language (XAML). Actualmente tenemos disponibl Xamarin.Forms Previewer o lo que es lo mismo, una herramienta que nos permite visualizar en tiempo real cualquier cambio relacionado con el código XAML que define la UI. Sin embargo, existen grandes opciones que vienen de parte de la comunidad y que vienen a extender y ayudar a la experiencia disponible directamente en Visual Studio. De esta forma tenemos propuestas como Gorilla Player o como la herramienta que va a cubrir toda la atención de este artículo, UI Seuth.

UI Sleuth

UI Sleuth es una herramienta creada por Michael Davis actualmente disponible en fase Beta para Windows (próximamente disponible también para MacOS) que nos permite depurar nuestras aplicaciones Xamarin.Forms. Entre las principales características permite:

  • Conectar y manejar un dispositivo.
  • Ver el árbol de elementos que componen la UI.
  • Hacer prototipos de UI de forma rápida y sencilla.
  • Detectar errores de UI.

Podemos resumir las características en un inspector visual (UI) en tiempo real de aplicaciones Xamarin.Forms.

¿Y en comparación con otras herramientas?

Xamarin Previewer permite ver directamente la renderización de una página XAML en Visual Studio. La aplicación no corre en un emulador o dispositivo, es una aproximación visible directamente en el IDE.

Por otro lado, tenemos a Gorilla Player. Esta gran herramienta permite ver los cambios en el codigo XAML de una página en tiempo real en N emuladores o dispositivos. La aplicación no se encuentra en ejecución.

La instalación

Empezamos por el principio, realizar la instalación de la herramienta. Debemos accerder a la página de UI Sleuth, rellenar el formulario básico con nombre y correo (recuerda, se encuentra en fase beta, la clave actualmente es probar y reportar feedback!).

Página de la herramienta
Página de la herramienta

Tras rellenar el formulario, accedemos a la página de descarga.

Descargar la herramienta
Descargar la herramienta

Para instalar basta con hacer doble clic sobre el archivo descargado. La instalación es un proceso rápido y sencillo.

Instalando
Instalando

Los requisitos de la herramienta son:

  • Microsoft Windows 7+ 64bit
    Visual Studio 2012+
    Xamarin.Forms 2.0+
    Emulador Android 4.03+ o dispositivo
    Android SDK (opcional)

Preparar la solución para utilizar UI Sleuth

Tras instalar la herramienta debemos de preparar nuestra aplicación para poder ver cambios al vuelo. En el proyecto Android, debemos hacer clic secundario en Administrar paquetes NuGet…

Buscamos por «UISleuth.Android»:

Añadir paquete NuGet

Instalamos el paquete. A continuación, en la actividad principal, MainActivity.cs añadimos el siguiente código tras la llamada a LoadApplication:

#if DEBUG
UISleuth.Inspector.Init();
#endif

Todo listo!. Lanzamos la aplicación en un emulador o dispositivo real.

Conectando

Continuamos arrancando la herramienta UISleuth. Nos aparecerá un listado de emuladores o dispositivos conectados. Podemos conectar directamente pulsando el botón Connect.

Conectando

También podemos realizar un proceso de conexión manual utilizando la dirección IP del dispositivo.

Tras conectar, veremos la pantalla principal de la herramienta:

UISleuth

Tenemos:

  • Visual Outline: Este panel situado a la izquierda nos permite explorar el árbol visual que compone la interfaz de usuario.
  • History: Sencillo panel que guarda cada paso de cada movimiento realizado en la herramienta. También situado a la izquierda.
  • Properties (Attached Properties y Events): Panel muy importante y completo. Tenemos la posibilidad de gestionar de forma visual las propiedades y eventos de cada control que compone la UI.

Utilizando UI Sleuth

Vamos a utilizar la herramienta para crear una sencilla vista de Login. Desde la propia herramienta podemos añadir los elementos visuales que necesitamos. Vamos a utilizar:

  • StackLayout
  • Label
  • Entry
  • Button
Añadir nuevos elementos

Tras añadir los elementos, tenemos la posibilidad de editar todas sus propiedades:

Gestión de propiedades

A la hora de añadir diferentes elementos de la UI, tenemos la posibilidad de modificar, arrastrar y soltar dentro del árbol visual para modificar el Layout de la aplicación:

Reordenar elementos de la UI

E incluso, podemos probar la aplicación y ver una previsualización en la misma herramienta:

Probando la App!

En resumen

Seguimos esperando un editor visual que permite la construcción de interfaces de usuario de forma sencilla, arrastrando y soltando, etc. Sin embargo, cada vez tenemos más opciones disponibles para la edición y gestión de la UI de nuestras aplicaciones Xamarin.Forms. UI Sleuth permite ver cambios de una aplicación en ejecución, además de poder acceder a listado de controles para añadir, poder ver el árbol visual pudiendo hacer drag & drop en el mismo, etc. En breve llegarán más características (versión MacOS, soporte a listados, etc.). Sin duda, una opción más que interesante la de Michael Davis que seguro a muchos os gustará y que seguirá mejorando en próximos meses.

¿Y a ti que te parece la herramienta?

Más información

[Evento] Xamarin Dev Days 2017 en Sevilla

El evento

Xamarin Dev Days son una serie de eventos celebrados a lo largo de todo el mundo que ofrecen la posibilidad de conocer las últimas novedades relacionadas con el desarrollo Xamarin, poder disfrutar de talleres y por supuesto, compartir momentos de networking.

Agenda

La agenda:

  • 9:00AM Registro y bienvenida.
  • 9:30 – 10:10AM Introducción a Xamarin. Introducción a los conceptos básicos tanto de Xamarin Classic como de Xamarin.Forms así como a la integración con Visual Studio, Xamarin Test Cloud, etc.
  • 10:20 – 11:00AM Xamarin Forms. Tras una introducción a conceptos básicos se verá como estructurar el proyecto y una serie de buenas prácticas para compartir la mayor cantidad de código posible. Finalmente se terminará con las últimas novedades como Xamarin.Forms Previewer, etc.
  • 11:10 – 11:50AM Xamarin + Azure. Gracias al Azure podemos crear servicios de backend, notificaciones push y otras características esenciales de forma compartida. En esta sesión aprenderemos como sacarle partido a Azure desde nuestras aplicaciones Xamarin.
  • 12:00 – 2:00PM Hands on Lab. ¿Crear una aplicación Xamarin paso a paso con MVVM, uso de servicios REST y accediendo a características de cada plataforma?. En este taller realizaremos una aplicación con esas características entre todos. Anímate y ven con tu portátil preparado!.
  • 2:00 – 3:00PM Comida.

La fecha

El evento tendrá lugar el próximo Sábado, 08 de Abril de 9:00h a 15:00h. Tendremos tres sesiones técnicas de  40 minutos de duración cada una junto a un taller de 2 horas de duración. Además contaremos con regalos (ya los hemos visto, son geniales!) y sorpresas.

¿Te apuntas?

El lugar

El evento se celebrará en las oficinas de Bitnami.

Av. de la República Argentina, 31b, Sevilla

Más información

[Xamarin.Forms] Aplicaciones Tizen

tizen_logoAplicaciones nativas multiplataforma

Xamarin.Forms es un framework que añade una capa de abstracción en la interfaz de usuario permitiendo crear aplicaciones multiplataforma nativas compartiendo el código de la UI escrito en XAML o C#.

Crear aplicaciones nativas, compartiendo grandes cantidades de código y llegar a varias plataformas son las claves del éxito Xamarin.Forms. Las plataformas soportadas actualmente son:

  • Android
  • iOS
  • Windows Phone
  • UWP

Entre la lista de peticiones y deseos relacionados con Xamarin.Forms, el acceder a más plataformas es una de las más destacadas.

Tizen aparece en escena

Tizen es un Sistema operative móviol basado en Linux, patrocinado por la Linux Foundation con el objetivo de sustentar una gran variedad de dispositivos, tabletas, móviles, wearables, dispositivos IoT, SmartTVs, etc.

Tizen en TVs
Tizen en TVs

Apoyado y utilizado por Samsung en gran variedad de dispositivos brilla por su ligereza (requiere hardware menos potente) ayudando al equilibro y consumo.

Actualmente disponible en más de 50 millones de dispositivos Samsung incluidas SmartTVs, wearables, dispositivos IoT y teléfonos.

El pasado Microsoft Connect 2016, en San Francisco, Samsung en colaboración con Microsoft anunciaba la primera versión en modo Preview de las herramientas Tizen para Visual Studio.

Tizen.NET
Tizen.NET

Las herramientas Tizen para Visual Studio facilitan emuladores, extensiones para Visual Studio con la posibilidad de tener IntelliSense y capacidades de depuración, se utiliza Xamarin.Forms para definir la interfaz de usuario, como ya hacíamos con iOS Y Android. De esta forma, Xamarin.Forms añade una nueva plataforma a la que dar soporte ampliando las posibilidades del mercado.

Tizen.NET, características y componentes

Tizen.NET se basa en un conjunto de componentes:

  1. .NET Core
  2. Forms como framework de UI
  3. APIs específicas de Tizen

.NET Core

.NET Core es la plataforma de desarrollo llevada a cabo por Microsoft y la comunidad .NET disponible en GitHub. Con la multiplataforma como clave destacada, soporta Windows. MacOS y Linux, además se puede utilizar en dispositivos, la nube o en escenarios emebebidos o IoT.

.NET Core contiene las siguientes partes:

  • El .NET runtime, otorga la carga de ensamblados, el garbage collector, interoperabilidad nativa, y otros servicios básicos.
  • Un conjunto de librerías que facilitan utilidades fundamentales, tipos de datos primitivos, etc.
  • Un conjunto de herramientas de SDK y de compiladores del lenguaje que permiten completar todas las necesidades en el desarrollo. Disponibles en el .NET Core SDK.

El uso de .NET native ofrece grandes ventajas:

  • Tiempos de ejecución más rápidos
  • Tiempos de arranque más rápidos
  • Coste bajo en despliegues
  • Optimización en el uso de memoria

Xamarin.Forms como framework de UI

Xamarin.Forms es un toolkit multiplataforma pata crear interfaces de usuario de forma eficiente y compartida entre iOS, Android y Windows. Tizen.NET soporta el 99% de características de Xamarin.Forms. Las limitaciones son:

  • AppLinkEntry
  • PinchGestureRecognizer
  • PanGestureRecognizer
  • OpenGLView
  • WebView
  • OnPlatform<T>
  • PlatformEffect<TContainer, TControl>

APIs específicas de Tizen

Tizen.NET nos permite trabajar con la plataforma utilizando C#, es decir, expone las APIs nativas de Tizen como las de localización o conectividad. Actualmente, utilizando C# tenemos soporte para el 60% de las APIs de Tizen.

tizen-architecture

Las APIs soportadas de Tizen son:

  • Applications: Aporta el conjunto de APIs relacionado con la aplicación tales como la gestión de eventos relacionados con el estado de la aplicación.
  • Content: Descarga de contenido, almacenamiento, asociaciones de tipos.
  • Location: Localización geográfica y geofencing.
  • Multimedia: Servicios multimedia incluido audio o grabación.
  • Network: Control del estado de la conectividad o accede a información de la red.
  • Security: Almacenamiento seguro para almacenar contraseñas o claves.
  • System: Servicios específicos del Sistema, obtener información del estado, información del sistema, etc.

La instalación

Comenzamos accediendo a la documentación oficial de Tizen.NET donde tenemos acceso al instalador.

Descarga
Descarga
Descargar Tizen.NET
Descargar Tizen.NET

Tras la descarga del archivo, debemos realizar la instalación de las herramientas Tizen.NET.

Setup
Setup

Al completar la instalación debemos tener todo lo necesario.

A tener en cuenta:

  • Se requiere una maquina con un procesador de 64 bits.
  • Para lanzar el emulador se requiere Intel HAXM. Hyper-V debe estar deshabilitado.

Creando una aplicación para Tizen desde Visual Studio

Las herramientas de Tizen para Visual Studio incluyen:

  • Emulador: Utiliza una imagen de Tizen 3.0 (beta) con soporte a aplicaciones .NET. Versiones anteriores del emulador a pesar de poder utilizarlos desde Visual Studio, no tienen soporte a .NET.
  • APIs: Sólo se añaden en la primera Preview APIs móviles.
  • Extensiones: Mediante dos extensiones se añade soporte a la edición, depuración o compilación del proyecto.
Extensiones Tizen en Visual Studio
Extensiones Tizen en Visual Studio

Vamos a crear un proyecto Xamarin.Forms con soporte a Tizen. A la hora de crear el proyecto, contamos con diferentes plantillas de proyectos.

Plantillas Tizen
Plantillas Tizen

Bajo la pestaña Tizen encontramos:

  • Blank App (Tizen Xamarin.Forms Portable): Crea una solución con una librería portable y un proyecto nativo Tizen.NET.
  • Blank App (Tizen Xamarin.Forms Single): Sólo crea el proyecto Tizen. Idóneo para añadir a una solución Xamarin.Forms ya preparada.
  • Class Library (Tizen): Librería con Soporte a Tizen.

Seleccionamos la primera opción. Tras crear el Proyecto veremos una solución con dos proyectos:

  • Un proyecto nombrado <projectname> de tipo portable que contiene todo el código Xamarin.Forms.
  • Otro proyecto llamado <projectname>.Tizen que contiene todo el código necesario para inicializar la aplicación con el framework Tizen.

Para desplegar la aplicación en el emulador bastara con pulsar F5 o pulsar el botón Play.

Compilar
Compilar

Tras unos segundos el icono de la aplicación debe aparecer en el emulador permitiendo el acceso a la misma.

La App desde la plantilla en ejecución
La App desde la plantilla en ejecución

NOTA: El primer arranque del emulador se encarga de desempaquetar la imagen necesario. El proceso puede tardar unos minutos.

A continuación, vamos a crear una aplicación con algo de entidad utilizando MVVM para mostrar un listado de monos.

Sin embargo, antes de comenzar, vamos a repasar la estructura del proyecto para conocer todo lo necesario relacionado. Vamos a ignorar el contenido de la librería portable al ser exactamente igual a todo lo que ya teníamos en Xamarin.Forms.

En el proyecto Tizen tenemos:

  • Una carpeta llamada shared que contendrá elementos relacionados con la aplicación. El icono de la aplicación se encuentra empaquetado en el directorio shared/res.
  • La carpeta res contendrá recursos para la aplicación. Por ejemplo, si la aplicación necesita un archivo en tiempo de ejecución, este es su lugar.
  • La carpeta lib contiene el código generado de la aplicación.

También tenemos un archivo de vital importancia, tizen-manifiest.xml. Hablamos del archivo de manifiesto, un archivo de configuración donde se especifican datos básicos de la aplicación como nombre, icono, paquete, etc.

Tras analizar la estructura del proyecto, continuamos con nuestra aplicación. Comenzamos creando la estructura base de carpetas:

tizen-project-structure

Continuamos creando el modelo de datos, una clase llamada Monkey que definirá a cada mono del listado:

public class Monkey
{
    public string Name { get; set; }

    public string Location { get; set; }

    public string Details { get; set; }

    public string Image { get; set; }
}

Continuamos definiendo la vista modelo, encargada de preparar toda la colección de monos a mostrar en la UI:

public class MonkeysViewModel
{

   public ObservableCollection<Monkey> Monkeys { get; set; }

    public MonkeysViewModel()
    {
        Monkeys = new ObservableCollection<Monkey>
        {
            new Monkey
            {
                Name = "Baboon",
                Location = "Africa & Asia",
                Details = "Baboons are African and Arabian Old World monkeys belonging to the genus Papio, part of the subfamily Cercopithecinae.",
                Image = "http://upload.wikimedia.org/wikipedia/commons/thumb/f/fc/Papio_anubis_%28Serengeti%2C_2009%29.jpg/200px-Papio_anubis_%28Serengeti%2C_2009%29.jpg"
            },
                  ...
            };
      }
}

Por último, llega el momento de definir la UI. Para ello utilizaremos la vista principal en XAML:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="TizenMonkeys.Views.MonkeysView"
             xmlns:templates="clr-namespace:TizenMonkeys.Views.Templates;assembly=TizenMonkeys"
             Title="Monkeys Tizen.NET">
    <ListView
        ItemsSource="{Binding Monkeys}"
        RowHeight="80"
        HasUnevenRows="true">
        <ListView.ItemTemplate>
            <DataTemplate>
                <ViewCell>
                    <templates:MonkeyItemView/>
                </ViewCell>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</ContentPage>

Si compilamos y ejecutamos el proyecto:

Voila!
Voila!

Impresionante, ¿verdad?

Recuerda, cualquier tipo de duda o sugerencia es bienvenida en los comentario del artículo. Tizen.Net nos abre un mundo de posibilidades de forma sencilla al abrirnos el abanico de dispositivos soportados. ¿Y tú que opinas?.

Tizen.Net, ¿qué es lo próximo?

El objetivo marcado es continuar añadiendo extensiones que faciliten la creación de grandes aplicaciones. Los puntos en el camino próximo son:

  • Mayor soporte a APIs de Tizen.
  • Añadir diferentes perfiles (TV, mobile).
  • Se subirá próximamente el código fuente (como el resto de Xamarin.Forms está disponible en GitHub).
  • Mayor cantidad de ejemplos utilizando Tizen.NET.

[Material dotNetMálaga] Taller Xamarin

logo_dotnetmlg_full_color_2tintas_2El evento

El pasado 10 de Diciembre tenía lugar el dotNetMálaga 2016. Evento destinado a desarrolladores en España con 2 Tracks diferentes, 10 sesiones técnicas, talleres técnicos y todo un día repleto de grandes momentos, tiempo para encontrar a viejos conocidos y conocer a muchos otros nuevos.

El material

He tenido la oportunidad de participar con un taller Xamarin donde realizamos una aplicación Xamarin.Forms paso a paso utilizando MVVM.

En cuanto al código fuente del taller, se encuentra disponible en GitHub:

Ver GitHub

Agradecer a toda la organización del eventos, ponentes y asistentes por hacerlo posible. El año que viene…¿repetimos?

Más información

[Xamarin.Forms] Un vistazo al uso de Native Views desde XAML

XamarinIntroducción

Xamarin.Forms añade una capa de abstracción sobre la capa de la interfaz de usuario que permite definir la misma una única vez para todas las plataformas con código C# o XAML. A pesar de la abstracción, podemos acceder a características específicas de la plataforma utilizando DependencyService o crear nuevos controles accediendo a características nativas gracias al uso de Custom Renders, efectos o Native Embbeding.

En el uso de Custom Renders o efectos, necesitamos crear una clase en la PCL con la definición del control o efecto y una clase en cada plataforma soportada con la implementación.

¿Y si se puede hacer todo de una forma más directa?

Native Views

Con Xamarin.Forms 2.3.3.+se introduce el concepto de Native Views. Se pueden incrustar directamente vistas nativas en el XAML de nuestra interfaz compartida ya sea utilizando proyectos compartidos o librerías portables.

Uso en Android

Añadir vistas nativas es realmente sencillo. Necesitamos declarar los namespaces XML (xmlns) necesarios:

xmlns:android="clr-namespace:Android.Widget;assembly=Mono.Android;targetPlatform=Android"
xmlns:androidForms="clr-namespace:Xamarin.Forms;assembly=Xamarin.Forms.Platform.Android;targetPlatform=Android"

Tras añadir los namespaces correspondientes, podemos acceder directamente a controles nativos:

<android:CalendarView         
     x:Arguments="{x:Static androidForms:Forms.Context}" />

Podemos acceder directamente a las propiedades del control nativo como atributos XML. En caso de requerir el uso de argumentos podemos establecerlo utilizando x:Arguments.

En Android es necesario pasar el contexto de Xamarin.Forms. En caso contrario obtendremos un error:

System.MissingMethodException: Default constructor not found for type Android.Widget.xxxxx

El resultado:

Calendario nativo de Android
Calendario nativo de Android

El turno de iOS

En el resto de plataformas, es practicamente igual el uso de Native Views. Comenzamos definiendo el namespace necesario:

xmlns:ios="clr-namespace:UIKit;assembly=Xamarin.iOS;targetPlatform=iOS"

Y podemos acceder directamente a controles nativos:

<ios:UIDatePicker />

El resultado:

UIDatePicker
UIDatePicker

Y ahora Windows

En el caso de Windows, igualmente necesitamos declarar el namespace:

xmlns:uwp="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"

Y tenemos acceso a vistas nativas:

<uwp:CalendarDatePicker />

El resultado:

nativeviews-windows
CalendarDatePicker

Utilizando Native Views

Tras definir tres vistas, cada una con vistas nativas de cada plataforma, para utilizarlas en una página debemos definir el namespace correspondiente:

xmlns:nativeViews="clr-namespace:NativeViews.Views.Native"

Y añadir cada vista:

<Grid>
     <nativeViews:AndroidView />
     <nativeViews:IosView />
     <nativeViews:WindowsView />
</Grid>

Sencillo, ¿cierto?.

¿Qué ocurre al ejecutar la aplicación y cargar esta vista en Android (por ejemplo)?.

Sólo se renderizarán los elementos Xamarin.Forms y la vista nativa de Android. No se mostrará nada relacionada con las visats nativas de iOS o Windows. Igual comportamiento obtendremos en otras plataformas.

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

Ver GitHub

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

Atener en cuenta

A la hora de utilizar Native Views en nuestros desarrollos hay que tener en cuenta que:

  • No se puede utilizar Native Views en una vista con XAMLC (compilación de XAML) activado.
  • No podemos utilizar x:Name en controles nativos.

one-more-thingOne more thing

La posibilidad de añadir directamente vistas nativas en el XAML de nuestras vistas compartidas en Xamarin.Forms es una nueva característica interesante pero en la mayoría de ocasiones nos interesará gestionar de una forma adecuada la interacción con los controles.

Native Views permite el uso de enlace a datos tanto en modo OneWay como TwoWay. Gracias a esta posibilidad podemos crear vistas realmente complejas de una forma más simple. En próximos artículos continuaremos profundizando en el concepto de Native Views centrándonos en el uso de enlace a datos. Permanece atento!

Más información

[Xamarin.Forms] Behavior para hacer scroll infinito

stack-04-wfIntroducción

Actualmente el control de tipo listado incluido en Xamarin.Forms incluye soporte a características avanzadas interesantes como el uso de plantillas diferentes para cada elemento del listado o la posibilidad de realizar Pull To Refresh.

A pesar de que el refresco de listados realizando Pull To Refresh es una forma bastante habitual y extendida, no es la única. También en ocasiones es necesario refrescar listados a la medida que se va realizando scroll, lo que se conoce como scroll infinito.

¿Cómo realizamos scroll infinito en Xamarin.Forms?

El evento ItemAppearing

El control ListView lanza el evento ItemAppearing cada vez que un elemento pasa a ser visible. Nuestra tarea para detectar por lo tanto si hemos llegado al final es que el último elemento visible es el último elemento disponible.

NOTA: Habitualmente siempre que sea posible se recomienda el uso de idntificadores para realizar las comprobaciones necesarias.

Creando un Behavior

Los Behaviors (o comportamientos en Español) nos permiten añadir lógica directamente en XAML para realizar acciones sin necesidad de escribir código extra en el code behind.

Vamos a crear un Behavior que asociado a un listado permita lanzar un comando llegado al final del scroll para solicitar nuevos elementos.

Comenzamos creando una clase que herede de Behavior<T>:

public class IniniteListScrollingBehavior : Behavior<ListView>
{

}

Debemos implementar:

  • Propiedad AssociatedObject: Control al que se adjuntará el Behavior, es decir, el ListView.
  • Método OnAttachedTo: Lanzado inmediatamente tras adjuntar el Behavior al control. Se recibe una referencia al control adjuntado idóneo para acceder a propiedades o eventos del mismo.
  • Método OnDetachingFrom: Lanzado cuando el Behavior se elimina del control. Lugar perfecto para realizar tareas de limpieza (gestión de suscripciones de eventos, liberar recursos, etc.).
public ListView AssociatedObject { get; private set; }

protected override void OnAttachedTo(ListView bindable)
{
     base.OnAttachedTo(bindable);

     AssociatedObject = bindable;
     bindable.BindingContextChanged += OnBindingContextChanged;
     bindable.ItemAppearing += OnItemAppearing;
}

protected override void OnDetachingFrom(ListView bindable)
{
     base.OnDetachingFrom(bindable);

     bindable.BindingContextChanged -= OnBindingContextChanged;
     bindable.ItemAppearing -= OnItemAppearing;
     AssociatedObject = null;
}

private void OnBindingContextChanged(object sender, System.EventArgs e)
{
     base.OnBindingContextChanged();
     BindingContext = AssociatedObject.BindingContext;
}

private void OnItemAppearing(object sender, ItemVisibilityEventArgs e)
{
     var listview = ((ListView)sender);

     if (listview.IsRefreshing)
          return;
}

Vamos a utilizar el método ItemAppearing del ListView para lanzar un comando encargado de refrescar el listado. Necesitamos crear una BindableProperty de tipo ICommand en el Behavior:

public static readonly BindableProperty CommandProperty =
     BindableProperty.Create("Command", typeof(ICommand), typeof(IniniteListScrollingBehavior), null);

public ICommand Command
{
     get { return (ICommand)GetValue(CommandProperty); }
     set { SetValue(CommandProperty, value); }
}

En el método ItemAppearing del control:

private void OnItemAppearing(object sender, ItemVisibilityEventArgs e)
{
     var listview = ((ListView)sender);

     if (listview.IsRefreshing)
          return;

     if (Command == null)
     {
          return;
     }

     if (Command.CanExecute(e.Item))
     {
          Command.Execute(e.Item);
     }
}

Accedemos al comando (siempre y cuando el listado no siga refrescando) y lo ejecutamos pasándole el último elemento visible como parámetro. Este parámetro lo utilizaremos desde la ViewModel para determinar si ese elemento es el último de la lista o no.

Utilizando el Behavior

Tras crear el Behavior ha llegado el momento de utilizarlo. Pero antes de lanzarnos de pleno…¿de dónde obtenemos la información?.

En la ViewModel tendremos una propiedad pública con el listado a mostrar:

private ObservableCollection<Monkey> _monkeys;

public ObservableCollection<Monkey> Monkeys
{
     get { return _monkeys; }
     set
     {
          _monkeys = value;
          RaisePropertyChanged();
     }
}

Utilizaremos también un par de propiedades para determinar:

  • IsBusy: Esta propiedad nos indicará cuando se esta realizando la carga de más información. Utilizada en la UI para mostrar un indicador visual de carga en caso necesario.
  • CurrentPage: Un entero que almacena el número de página utilizado al cargar información. Gran cantidad de APIs soportan paginación. En la mayoría de ocasiones necesitamos indicar el número de página a cargar (justo esta propiedad) y el número de elementos por página.
public bool IsBusy { get; set; }

public int CurrentPage { get; set; }

En nuestro ejemplo, para simplificar todo en la medida de lo posible y centrarnos en el uso del Behavior, vamos a cargar datos locales:

private void LoadItems(int pageSize = 10)
{
     IsBusy = true;

     if(Monkeys == null)
     {
          Monkeys = new ObservableCollection<Monkey>();
     }

     for (int i = CurrentPage; i < CurrentPage + pageSize; i++)
     {
          Monkeys.Add(new Monkey()
          {
               MonkeyId = i + 1,
               Name = string.Format("Monkey {0}", i + 1)
          });
     }

     CurrentPage = Monkeys.Count;
     IsBusy = false;
}

El Behavior utilizará un comando para refrescar la información:

private ICommand _refreshCommand;

public ICommand RefreshCommand
{
     get { return _refreshCommand = _refreshCommand ?? new DelegateCommand<Monkey>(RefreshCommandExecute, RefreshCommandCanExecute); }
}

public bool RefreshCommandCanExecute(Monkey monkey)
{
     return !IsBusy && 
            Monkeys.Count != 0 && 
            Monkeys.Last().MonkeyId == monkey.MonkeyId;
}

public void RefreshCommandExecute(Monkey monkey)
{
     LoadItems();
}

Para utilizarlo en nuestra UI compartida XAML, debemos crear un namespace:

xmlns:behavior="clr-namespace:InfiniteScrollingBehavior.Behaviors;assembly=InfiniteScrollingBehavior"

Utilizando la propiedad Behaviors del listado, adjuntamos el Behavior creado enlazando con el comando previamente visto.

<ListView
     ItemsSource="{Binding Monkeys}"
     IsRefreshing="{Binding IsBusy}"
     HasUnevenRows="true">
     <ListView.ItemTemplate>
          <DataTemplate>
               <ViewCell>
                    <templates:MonkeyTemplate />
               </ViewCell>
           </DataTemplate>
       </ListView.ItemTemplate>
       <ListView.Behaviors>          
          <behavior:IniniteListScrollingBehavior  
                Command="{Binding RefreshCommand}">
          </behavior:IniniteListScrollingBehavior>
     </ListView.Behaviors>
</ListView>

El resultado:

inifinitescrollingbehavior

Sencillo y además facilmente reutilizable.

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

Ver GitHub

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

Más información

[Xamarin.Forms] Propiedades específicas desde XAML para Windows Desktop y Mobile

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
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.

La propiedad OS permite identificar la plataforma en la que se ejecuta la aplicación. Puede ser un valor de la enumeración TargetPlatform con uno de los siguientes valores:

  • iOS
  • Android
  • WinPhone (Windows 8 Silverlight)
  • Windows (Windows 8.1, Windows Phone 8.1 y UWP)

Permite de forma sencilla y efectiva personalizar la interfaz o la lógica de la aplicación en base a la plataforma:

if (Device.OS == TargetPlatform.iOS) 
{
    stackLayout.Padding = new Thickness (0, 20, 0, 0);
}

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. Device.OnPlatform es un método que cuenta con uno de los siguientes parámetros opcionales:

  • iOS
  • Android
  • WinPhone
<BoxView 
     WidthRequest="100"
     HorizontalOptions="Center">
  <BoxView.Color>
    <OnPlatform x:TypeArguments="Color"
      iOS="Green"
      Android="#738182"
      WinPhone="Accent" />
  </BoxView.Color>
</BoxView>

Lo visto esta genial pero hay un pequeño detalle que nos puede causar problemas…

¿Qué ocurre si deseamos personalizar la UI desde XAML y dinstinguir entre Windows Phone y Windows?.

La clase OnPlatform no nos facilita la gestión completa. Siempre nos queda la posibilidad de hacer lo necesario desde código C# pero aspectos directamente relacionados con la UI sería fantástico poder llevarlo a cabo desde la propia UI o lo que es lo mismo desde código XAML.

CustomOnPlatform

Aquí es donde entra en juego un pequeños helper creado en código C#:

public sealed class CustomOnPlatform<T>
{
     public CustomOnPlatform()
     {
         Android = default(T);
         iOS = default(T);
         WinPhone = default(T);
         Windows = default(T);
     }

     public T Android { get; set; }

     public T iOS { get; set; }

     public T WinPhone { get; set; }

     public T Windows { get; set; }

     public T Other { get; set; }

     public static implicit operator T(CustomOnPlatform<T> onPlatform)
     {
         switch (Xamarin.Forms.Device.OS)
         {
             case Xamarin.Forms.TargetPlatform.Android:
                 return onPlatform.Android;
             case Xamarin.Forms.TargetPlatform.iOS:
                 return onPlatform.iOS;
             case Xamarin.Forms.TargetPlatform.WinPhone:
                 return onPlatform.WinPhone;
             case Xamarin.Forms.TargetPlatform.Windows:
                 if (Xamarin.Forms.Device.Idiom == Xamarin.Forms.TargetIdiom.Desktop)
                     return onPlatform.Windows;
                 else
                     return onPlatform.WinPhone;
         }
     }
}

Sencillo. Utilizamos la propiedad OS de la clase Device para determinar la plataforma en la que estamos. En el caso de Windows, dado que nos indica el mismo valor en UWP (tanto si estamos en el Desktop como Mobile) hacemos uso combinado con la propiedad Idiom.

La propiedad Idiom tiene como objetivo fundamental determinar si la aplicación se ejecuta en teléfono, tableta o escritorio. Puede contener unos de los siguientes valores de la enumeración TargetIdiom:

  • Desktop: Detecta aplicación UWP en Windows 10.
  • Phone: La aplicación corre en iPhone, iPod Touch, móvil Android o Windows Phone.
  • Tablet: iPad, tableta Android o Windows 8.1.

De esta forma, además de detectar cuando se ejecuta en iOS y Android, podremos detectar cuando se ejecuta en Windows Phone y en Windows (UWP) tanto en escritorio como móvil.

Para usar el helper desde XAML, definimos el namespace:

xmlns:helpers="clr-namespace:CustomOnPlatform.Helpers;assembly=CustomOnPlatform"

Y lo usamos tal y como usaríamos OnPlatform:

<BoxView 
     HorizontalOptions="Center"
     VerticalOptions="Center"
     HeightRequest="100"
      WidthRequest="100">
     <BoxView.BackgroundColor>
         <helpers:CustomOnPlatform  
             x:TypeArguments="Color"
             Android="Red"
             iOS="Blue"
             WinPhone="Yellow"            
             Windows="Green"/>
     </BoxView.BackgroundColor>
</BoxView>

El resultado de la ejecución anterior es, cuadrado rojo en Android, azul en iOS, amarillo en Windows Phone (Silverlight) y Windows Mobile (UWP) siendo por último, verde en Windows escritorio.

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

Ver GitHub

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

Más información

[Curso CampusMVP] Desarrollo de aplicaciones móviles nativas multiplataforma con C# y Xamarin

Introducción

No hay duda, los smartphones y tabletas han llegado, y ya forman parte de la vida de todos nosotros. En muchos aspectos de nuestra vida accedemos a información, realizamos algna tarea o sencillamente nos entretenemos con uno de ellos.

Esto también nos afecta como desarrolladores. El desarrollo móvil se ha convertido en una prioridad en una gran mayoria de ámbitos.

Actualmente contamos con varias plataformas dominantes:

  • iOS
  • Android
  • Windows

El mercado móvil actual

Esto a nivel de desarrollo nos supone barreras. Contamos con múltiples plataformas de desarrollo diferentes, lenguajes diferentes, etc. suponiendo un nivel de aprendizaje y esfuerzo cada vez mayor de cara a desarrolladores. Además, la experiencia nos ha demostrado que los usuarios no aceptan aplicaciones no nativas. Buscan aplicaciones rápidas, fluidas y con consumos moderados perfectamente adaptadas a la plataforma, ofreciendo una experiencia completa.

¿Qué podemos hacer para mejorar este panorama?

Xamarin

Aquí es donde entran en juego las tecnologías multiplataforma. En estos últimos años han aparecido diversas tecnologías que permiten aprovechar una base de código en multiples plataformas. Entre diversas opciones brilla con especial fuerza, por diferentes motivos (evolución, comunidad, apoyo de Microsoft) Xamarin.

Xamarin es una plataforma de desarrollo gratuita de Microsoft que permite crear nuestras aplicaciones móviles una sola vez y que funcionen en Android, iOS y Windows entre otros sistemas (Windows 10, Mac, wearables…).

Las apps que creamos con Xamarin son aplicaciones nativas, es decir, no tienen diferencias con las que podríamos crear usando las herramientas propias de cada plataforma.

xamarinmonkeysCurso completo Xamarin en CampusMVP

Tras diferentes proyectos o eventos relacionados con Xamarin donde he podido aprender y compartir con toda la comunidad, ¿algo mejor que abordar todo lo posible en un curso completo?. Así nace el curso de desarrollo de aplicaciones móviles nativas multiplataforma con C# y Xamarin en CampusMVP.

El curso, organizado en diferentes módulos comienza realizando una introducción al desarrollo Xamarin, desde la instalación y requisitos iniciales a la primera aplicación. Continua con todos los conceptos básicos de interfaces de usuario tanto nativas como con Xamarin.Forms. Se realiza una parada obligatorio en el repaso de conceptos como los principios SOLID para acabar implementando el patrón MVVM desde cero y conocer MVVMCross. Tras todo este bloque de conceptos básicos profundizamos más con controles más pesados y complejos (listados o Custom Renders en Xamarin.Forms), navegación o el acceso a la plataforma (creación de servicios, controles personalizados, sensores, uso de bases de datos, etc.). Cerramos con la publicación de aplicaciones en las diferentes tiendas no sin antes conocer como crear pruebas unitarias, Xamarin.UITest y el uso de Test Cloud por ejemplo.

Estamos ante un curso que cubre tanto Xamarin.Classic como Xamarin.Forms (iOS, Android y Windows), con más de 200 lecciones, 60 videos, decenas de ejemplos, pruebas y tests, etc. Por si fuese poco, en el último de los módulos se desarrolla desde cero una aplicación real completa para iOS y Android, que repasa la mayor parte de las técnicas y conceptos vistos en el resto del curso: patrones de navegación, animaciones en Xamarin.Forms, uso de servicios REST, RSS, SQLite, MVVM, IoC…

Si te resulta interesante, puedes acceder a más información en el siguiente enlace. Espero sin duda que ayude a crecer la comunidad de desarrolladores Xamarin. Estoy deseando ver vuestras aplicaciones!

Como de costumbre, si tienes dudas o sugerencias relacionadas con la entrada, no dudes en usar los comentarios.

Más información

[Material] Xamarin Dev Days Sevilla

El evento

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

highres_454015178

El resultado fue un fantástico día de desarrollo Xamarin con la asistencia de todos los asistentes registrados, 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.Forms:

Comenzamos por una introducción de conceptos básicos, primera demo aplicando MVVM y terminamos repasando todas las últimas novedades como DataPages, Native Embedding o la creación de de efectos.

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

Ver GitHub

Quisiera terminar añadiendo algunos agradecimientos. Comienzo por los chicos de WorkINCompany por las instalaciones y toda la ayuda; a Plain Concepts y DevsDNA por su patrocinio y permitir contar con la sala y comida para los asistentes (muchas gracias); a SyncFusion y Xamarin también por patrocinar el evento, los asistentes quedaron encantados con los goodies y licencias; a mis compañeros Josué Yeray y Marcos Cobeña por sus demos y ayudar con todo y por supuesto a los asistentes. Gracias a todos, no es muy habitual que todos y cada uno de los registrados asistan. Si además están todo el día y con preguntas constantes, hace que todo sea tan apasionante y divertido que…¿cuándo la próxima?.

Más información