[Xamarin.Forms] Transiciones entre páginas

Introducción

En todas las plataformas, las aplicaciones móviles incluyen animaciones que otorgan movimiento, fluidez y focalizan la atención del usuario en las zonas deseadas. Actualmente no son un extra o añadido en las aplicaciones, sino una parte importante en la experiencia y usabilidad de las mismas.

Como desarrolladores, debemos no solo cuidar por supuesto el correcto funcionamiento de la aplicación, sino que debemos preocuparnos también por la usabilidad y la experiencia otorgada, donde entran en juego las animaciones.

Entre el conjunto de posibilidades a la hora de animar elementos, las transiciones entre páginas son un punto destacado para conseguir trasmitir fluidez y sensación de continuidad.

¿Cómo aplicamos transiciones entre páginas en Xamarin.Forms?.

En este artículo, vamos a ver como aplicar diferentes transiciones entre páginas en aplicaciones Xamarin.Forms.

Navegar entre páginas y animaciones

Entre los diferentes patrones habituales utilizados en el desarrollo móvil, el más utilizado es la navegación en pila. En Xamarin.Forms la clase NavigationPage ofrece una experiencia de navegación jerárquica donde el usuario puede navegar a través de las páginas tanto hacia delante como hacia atrás.

Para navegar de una página a otra, la aplicación añadirá (push) una nueva página en el navigation stack o pila de navegación.

await Navigation.PushAsync (new Page());

Para navegar atrás, a la página anterior, la aplicación eliminará (pop) la página actual de la pila de navegación, y a partir de ese momento la última página disponible en la pila pasará a ser la página activa.

await Navigation.PopAsync ();

Por defecto, tanto al navegar hacia delante como hacia atrás, se aplica una transición entre páginas. Podemos desactivar la animación tanto al navegar hacia delante:

await Navigation.PushAsync (new Page(), false);

Como hacia atrás:

await Navigation.PopAsync (false);

Transiciones personalizadas

Pero…¿y si necesitamos/queremos aplicar una transición personalizada en nuestra aplicación?. Las transiciones entre páginas son un aspecto bien cubierto en cada plataforma. Accediendo a cada una de ellas, con código específico por plataforma, podremos crear experiencias personalizadas. Si, lo habrás imaginado, vamos a utilizar un Custom Renderer.

En nuestra librería portable o net standard, comenzamos creando un nuevo control que hereda de NavigationPage:

public class TransitionNavigationPage : NavigationPage
{
     public TransitionNavigationPage() : base()
     {
 
     }

     public TransitionNavigationPage(Page root) : base(root)
     {

     }
}

Necesitamos determinar que animación entre un conjunto deseamos aplicar. Para conseguir este objetivo, primero creamos una enumeración con todos los tipos diferentes de transiciones que podremos utilizar:

public enum TransitionType
{
     Fade,
     Flip,
     Scale,
     SlideFromLeft,
     SlideFromRight,
     SlideFromTop,
     SlideFromBottom
}

Creamos una BindableProperty en nuestro control:

public static readonly BindableProperty TransitionTypeProperty =
     BindableProperty.Create("TransitionType", typeof(TransitionType), typeof(TransitionNavigationPage), TransitionType.SlideFromLeft);

public TransitionType TransitionType
{
     get { return (TransitionType)GetValue(TransitionTypeProperty); }
     set { SetValue(TransitionTypeProperty, value); }
}

Todo listo en nuestro control!.

Nuestra interfaz de usuario será sumamente simple, un listado de botones.

Nuestra interfaz

En cada caso, vamos a navegar a una página de detalles aplicando el tipo de animación correspondiente a cada botón.

Utilizaremos nuestro control TransitionNavigationPage en lugar de NavigationPage:

MainPage = new TransitionNavigationPage(new MainView());

Utilizar una transición u otra es tan sencillo como establecer la propiedad TransitionType (desde XAML o C#):

transitionNavigationPage.TransitionType = TransitionType.Fade;

Transiciones en Android

En Android, desde Lollipop, se dedicó un gran esfuerzo en mejorar el sistema de transiciones disponibles. Con la llegada de Material Design, llegó una nueva oleada de opciones. Tenemos la posibilidad de aplicar transiciones entre Activities y entre Fragments.

Utilizaremos la clase FragmentTransaction que nos permite realizar diferentes operaciones entre las que se encuentra, establecer la animación a aplicar en la transición utilizando el método SetCustomAnimations.

Transiciones en iOS

En el caso de iOS, aunque dependerá del tipo de animación a aplicar, utilizaremos principalmente la clase CATransition. Esta clase permite trabajar con la funcionalidad de animaciones del Core pudiendo aplicar una animación a un Layer completo.

Código específico de plataforma

En cada plataforma, vamos a crear una nueva clase TransitionNavigationPageRenderer donde realizaremos la implementación de nuestro Custom Renderer.

[assembly: ExportRenderer(typeof(TransitionNavigationPage.Controls.TransitionNavigationPage), typeof(TransitionNavigationPageRenderer))]
namespace TransitionNavigationPage.iOS.Renderers
{
     public class TransitionNavigationPageRenderer : NavigationRenderer
     {
 
     } 
}

En Android, utilizaremos el método SetupPageTransition para modificar la animación a utilizar.

protected override void SetupPageTransition(FragmentTransaction transaction, bool isPush)
{
     switch (_transitionType)
     {
          case TransitionType.Fade:
          break;
          case TransitionType.Flip:
          break;
          case TransitionType.Scale:
          break;
          case TransitionType.SlideFromLeft:
          break;
          case TransitionType.SlideFromRight:
          break;
          case TransitionType.SlideFromTop:
          break;
          case TransitionType.SlideFromBottom:
          break;
          default:
          break;
     }
}

En el caso de iOS, utilizaremos los métodos PushViewController y PopViewController para detectar cuando navegamos hacia delante y hacia atrás para aplicar animaciones personalizadas.

Fade

Comenzamos aplicando una de las animaciones más sencillas y posiblemente utilizadas en muchas ocasiones, Fade.

En Android, creamos un archivo XML con la definición de la animación en la carpeta anim dentro de Resources.

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:fillAfter="true">
 <alpha
      android:duration="1000"
      android:fromAlpha="0.0"
      android:toAlpha="1.0" />
</set>

Utilizamos el recurso en el método SetupPageTransition en nuestro Custom Renderer:

transaction.SetCustomAnimations(Resource.Animation.fade_in, Resource.Animation.fade_out);

En el caso de iOS:

  • Establecemos la propiedad Alpha a cero.
  • Utilizando el método Animate, animamos Alpha para establecerlo a valor 1.0.
View.Alpha = 0.0f;
View.Transform = CGAffineTransform.MakeIdentity();

UIView.Animate(0.5f, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Alpha = 1.0f;
     },
     null
);

Sencillo, ¿no?.

Flip

En Android, volvemos a crear la animación:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
 <scale
      android:interpolator="@android:anim/linear_interpolator"
      android:fromXScale="0.0"
      android:toXScale="1.0"
      android:fromYScale="0.7"
      android:toYScale="1.0"
      android:fillAfter="false"
      android:startOffset="200"
      android:duration="200" />
 <translate
      android:fromXDelta="50%"
      android:toXDelta="0"
      android:startOffset="200"
      android:duration="200"/>
</set>

Jugamos con la escala y translación de la vista para conseguir el “efecto óptico” buscado. Y la aplicamos de nuevo, utilizando SetCustomAnimations:

transaction.SetCustomAnimations(Resource.Animation.fade_in, Resource.Animation.fade_out);

En iOS, aplicamos una animación donde aplicamos una transformación a la View.

var m34 = (nfloat)(-1 * 0.001);
var initialTransform = CATransform3D.Identity;
initialTransform.m34 = m34;
initialTransform = initialTransform.Rotate((nfloat)(1 * Math.PI * 0.5), 0.0f, 1.0f, 0.0f);

View.Alpha = 0.0f;
View.Layer.Transform = initialTransform;
UIView.Animate(0.5f, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Layer.AnchorPoint = new CGPoint((nfloat)0.5, 0.5f);
          var newTransform = CATransform3D.Identity;
          newTransform.m34 = m34;
          View.Layer.Transform = newTransform;
          View.Alpha = 1.0f;
     },
     null
);

NOTA: Utilizando ObjectAnimator podemos aplciar transformaciones mucho más efectivas para realizar esta animación en Android. Fue introducido con API Level 11 (Android 3.0).

Scale

Animación en Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:fillAfter="true">
 <alpha
      android:duration="100"
      android:fromAlpha="0.0"
      android:toAlpha="1.0" />
 <scale
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:duration="1000"
      android:fromXScale="0.5"
      android:fromYScale="0.5"
      android:pivotX="50%"
      android:pivotY="50%"
      android:toXScale="1.0"
      android:toYScale="1.0" />
</set>

Y en iOS:

View.Alpha = 0.0f;
View.Transform = CGAffineTransform.MakeScale((nfloat)0.5, (nfloat)0.5);

UIView.Animate(duration, 0, UIViewAnimationOptions.CurveEaseInOut,
     () =>
     {
          View.Alpha = 1.0f;
          View.Transform = CGAffineTransform.MakeScale((nfloat)1.0, (nfloat)1.0);
     },
     null
);

En ambos casos, jugamos con la opacidad y la escala de la vista.

 

SlideFromLeft

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="-100%" android:toXDelta="0%"
          android:fromYDelta="0%" android:toYDelta="0%"
          android:duration="300"/>
</set>

De las animaciones más sencillas, una simple translación.

En iOS:

var transition = CATransition.CreateAnimation();
transition.Duration = 0.5f;
transition.Type = CAAnimation.TransitionPush;
transition.Subtype = CAAnimation.TransitionFromLeft;
View.Layer.AddAnimation(transition, null);

Aprovechamos CATransition para aplicar animaciones de transición desde izquierda, derecha

SlideFromRight

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
      <translate
           android:fromXDelta="100%" android:toXDelta="0%"
           android:fromYDelta="0%" android:toYDelta="0%"
           android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromLeft;

SlideFromTop

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="0%" android:toXDelta="0%"
          android:fromYDelta="-100%" android:toYDelta="0%"
          android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromTop;

SlideFromBottom

En Android:

<?xml version="1.0" encoding="utf-8" ?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
 android:shareInterpolator="false">
     <translate
          android:fromXDelta="0%" android:toXDelta="0%"
          android:fromYDelta="100%" android:toYDelta="0%"
          android:duration="300" />
</set>

En iOS:

transition.Subtype = CAAnimation.TransitionFromBottom;

Puedes descargar el ejemplo realizado desde GitHub:

Ver GitHub

¿Más opciones?

Realizando una combinación de las opciones realizadas podemos abordar muchas otras transiciones habituales. Existen otro tipo de transiciones que han ido ganando peso en Material Design, etc.

Por ejemplo, en Android hablamos de opciones como CircularReveal.

Circular Reveal

Podemos conseguir este efecto utilizando el método CreateCircularReveal disponible en ViewAnimationUtils.

En próximos artículos, podemos abordar de nuevo este punto viendo otras opciones como la anterior. ¿Qué transiciones sueles utilizar?, ¿cuál te gustaría ver?. Recuerda, cualquier duda o comentario es bienvenida en los comentarios de la entrada.

Más información

[Material] dotNetMálaga 2017

El evento

El pasado sábado 30 de Septiembre, tenía lugar en Málaga la dotNetMálaga 2017. Una jornada con múltiples sesiones técnicas en varios tracks, talleres, regalos, sorteos y mucho networking.

El resultado fue un fantástico día de comunidad con muchas preguntas, ayuda y tiempo para charlar entre todos.

El material

Pude participar en el evento con una de las sesiones técnicas hablando de pequeños trucos y consejos para conseguir el mejor rendimiento posible en Xamarin.Forms junto con un taller de desarrollo de aplicaciones para Hololens.

Rendimiento y Xamarin.Forms

¿Sabes el ciclo de vida de un Layout?, ¿qué opciones de Layout son más óptimas?, ¿cómo afectan los Bindings al rendimiento y como tratarlos?, ¿rendimiento en listados?, ¿fast renderers?, ¿que tener en cuenta al crear Custom Renders o Behaviors?. Intentamos dar respuesta a todas estas preguntas en esta sesión.

Puedes descargar todos los ejemplos utilizados desde GitHub:

Ver GitHubTaller Hololens con Wave Engine

Por la tarde continuamos con un divertido taller realizando una aplicación para Hololens con Wave Engine. Nuestro objetivo fue crear el sistema solar con posibilidad de realizar Air Tap sobre cada planeta para obtener información además de hacer uso de comandos de voz.

HoloPlanets

En el taller vimos:

  • Crear App 3D con el sistema solar.
  • Uso de modelos 3D.
  • Crear proyectos para HoloLens.
  • Gestión de cámara y posicionamiento.
  • Air Tap.
  • Comandos de voz.

La presentación utilizada:

Puedes descargar cada paso dado en el taller desde GitHub:

Ver GitHub

Gracias a ponentes y patrocinadores, por supuesto a todos los asistentes y mi enhorabuena a toda la organización. ¿Vamos pensando en la dotNetMálaga 2018?.

Más información

[Xamarin.Forms] Personalizando la NavigationPage: colores, fuentes, imágenes y más!

La NavigationPage

Xamarin.Forms cuenta con diferentes páginas que aportan diferentes experiencias de navegación.

Entre las opciones, una de las más utilizadas es la experiencia ofrecida por NavigationPage. Esta experiencia de navegación consiste en una navegación jerárquica e la que el usuario puede navegar por las páginas hacia delante y hacia atrás. Se implementa la navegacion como una pila de páginas (LIFO).

El layout de la NavigationPage

El layout de la NavigationPage es dependiente de cada plataforma:

  • En iOS, se utiliza una barra de navegación en la parte superior de la página mostrando título, y si es necesario, el botón de navegar atrás.
  • En Android, se muestra también una barra de navegación en la parte superior con título, icono y botón de navegar atrás.
  • En Windows, se muestra el título en una CommandBar situada en la parte superior. El botón de navegación atrás aparecerá en la barra de título de la ventana en escritorio y el botón virtual atrás en la parte inferior en móvil.

¿Cómo podemos personalizar la barra de navegación?

En este artículo vamos a aprender como:

  • Ocultar la barra de navegación.
  • Personalizar colores de la barra, del título, etc.
  • Añadir un logo personalizado en la barra de navegación.
  • Personalizar la fuente del título.
  • Personalizar el icono del botón atrás.

Ocultar la barra de navegación

En determinadas páginass puede interesarnos no mostrar la barra de navegación. Es bastante sencillo controlar este comportamiento gracias al método SetHasNavigationBar.

En C#:

NavigationPage.SetHasNavigationBar(this, false);

En XAML:

NavigationPage.HasNavigationBar="False"

Personalizando colores

Continuamos con otra necesidad bastante común en muchas aplicaciones, personalizar el color de fondo y del título de la aplicación.

Con la propiedad BarBackgroundColor podemos definir el color de fondo de la barra de navegación mientras que con la propiedad BarTextColor definimos el color del título.

<NavigationPage 
     xmlns="http://xamarin.com/schemas/2014/forms"
     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
     BarBackgroundColor="{StaticResource BarBackgroundColor}" 
     BarTextColor="{StaticResource AccentColor}" >
</NavigationPage>

El resultado en Android:

Personalización de colores en Android

En iOS:

Personalización de colores en iOS

Y UWP:

Personalizando colores en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Fuente personalizada en el título

El uso de fuentes personalizadas suele ayudar a potenciar la imagen de marca en muchas aplicaciones móviles. Existen multitud de casos donde el título de la barra de navegación cuenta con una fuente personalizada aplicada.

¿Cómo logramos esto?.

Vamos a necesitar código específico en cada plataforma. Entran en juego los Custom Renderers.

Android

En Android comenzamos añadiendo la fuente a utilizar en la carpeta de Assets.

Tras añadir la fuente, creamos un Custom Render.

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.Droid.Renderers
{
     public class CustomNavigationPageRenderer : NavigationPageRenderer
     {
           private Android.Support.V7.Widget.Toolbar _toolbar;

           public override void OnViewAdded(Android.Views.View child)
           {
                base.OnViewAdded(child);

                if (child.GetType() == typeof(Android.Support.V7.Widget.Toolbar))
                {
                     _toolbar = (Android.Support.V7.Widget.Toolbar)child;
                     _toolbar.ChildViewAdded += OnToolbarChildViewAdded;
                }
          }

          protected override void Dispose(bool disposing)
          {
               base.Dispose(disposing);

               if(disposing)
               {
                     _toolbar.ChildViewAdded -= OnToolbarChildViewAdded;
               }
          }

          private void OnToolbarChildViewAdded(object sender, ChildViewAddedEventArgs e)
          {
               var view = e.Child.GetType();

               if (e.Child.GetType() == typeof(Android.Widget.TextView))
               {
                    var textView = (Android.Widget.TextView)e.Child;
                    var spaceFont = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, "Trashtalk.ttf");
                    textView.Typeface = spaceFont;
                     _toolbar.ChildViewAdded -= Toolbar_ChildViewAdded;
               }
          }
     }
}

Accedemos a la Toolbar (tras ser añadida) y posteriormente accedemos a sus elementos hijos, para acceder al título (TextView) y modificar la fuente.

Este mismo proceso será muy similar en otras plataformas.

iOS

También vamos a necesitar un Custom Render:

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.iOS.Renderers
{
     public class CustomNavigationPageRenderer : NavigationRenderer
     {
          protected override void OnElementChanged(VisualElementChangedEventArgs e)
          {
               base.OnElementChanged(e);

              if (e.NewElement != null)
              {
                   var textAttributes = new UITextAttributes();
                   textAttributes.Font = UIFont.FromName("Trashtalk", 20);
                   UINavigationBar.Appearance.SetTitleTextAttributes(textAttributes);
              }
          }
     }
}

Bastante sencillo. Accedemos a la NavigationBar en iOS y aprovechamos el método SetTitleTextAttributes para personalizar la fuente.

UWP

Y llegamos a Windows.

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(CustomNavigationPageRenderer))]
namespace CustomFontsNavigationPage.UWP.Renderers
{
     public class CustomNavigationPageRenderer : NavigationPageRenderer
     {
          private CommandBar _commandBar;

          public CustomNavigationPageRenderer()
          {
                ElementChanged += OnElementChanged;
          }

          private void OnElementChanged(object sender, VisualElementChangedEventArgs e)
          {
               ElementChanged -= OnElementChanged;
               ContainerElement.Loaded += OnContainerElementLoaded;
          }

          private void OnContainerElementLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
          {
               ContainerElement.Loaded -= OnContainerElementLoaded;
               _commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
               var commandBarContent = ((Border)_commandBar.Content);
               var textBlock = commandBarContent.Child as TextBlock;

               if(textBlock != null)
               {
                    textBlock.FontFamily = new FontFamily("/Assets/Trashtalk.ttf#Trashtalk");
               }
           }
     }
}

Muy similar a lo realizado anteriormente en otras plataformas. Accedemos a la CommandBar utilizada como barra de navegación. A continuación, buscamos el TextBlock utilizado como título y utilizando la propiedad FontFamily, modificamos la fuente.

NOTA: Se puede definir el Element o parte compartida del Custom Renderer donde definir propiedades como el nombre de la fuente a utilizar o propiedades de tipo bool para aplicar la fuente personalizada o no en base a necesidades de cada página y hacer todo más personalizable.

El resultado en Android:

Fuente personalizada en título

En iOS:

Fuente personalizada en iOS

Y en Windows:

Fuente personalizada en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

Añadir un logo

Hemos hablado previamente del uso de fuentes personalizadas para conseguir trasmitir mejor la imagen de marca. Otro uso habitual es el de incluir una imagen (logo) en la barra de navegación.

¿Cómo lo conseguimos?.

Android

En Android podemos modificar el contenido de la Toolbar utilizando un layout AXML. Tras añadir la imagen a utilizar como logo en los recursos del proyecto, definimos un nuevo Layout:

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

Sólo faltaría utilizarlo desde nuestra actividad principal:

ToolbarResource = Resource.Layout.ToolbarLogo;

iOS

En el caso de iOS, conseguimos el resultado con un sencillo Custom Render:

[assembly: ExportRenderer(typeof(MainView), typeof(LogoPageRenderer))]
namespace CustomLogoNavigationPage.iOS.Renderers
{
     public class LogoPageRenderer : PageRenderer
     {
          public override void ViewWillAppear(bool animated)
          {
               base.ViewWillAppear(animated);

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

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

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

Accedemos a la NavigationBar y añadimos un UIImage utilizando la propiedad TitleView.

Windows

En Windows, el Custom Render necesario es muy similar a lo visto previamente:

[assembly: ExportRenderer(typeof(CustomNavigationPage), typeof(LogoPageRenderer))]
namespace CustomLogoNavigationPage.UWP.Renderers
{
     public class LogoPageRenderer : NavigationPageRenderer
     {
          private CommandBar _commandBar;

          public LogoPageRenderer()
          {
               ElementChanged += OnElementChanged;
          }

          private void OnElementChanged(object sender, VisualElementChangedEventArgs e)
          {
               ElementChanged -= OnElementChanged;
               ContainerElement.Loaded += OnContainerElementLoaded;
          }

          private void OnContainerElementLoaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
          {
               ContainerElement.Loaded -= OnContainerElementLoaded;
               _commandBar = typeof(PageControl).GetTypeInfo().GetDeclaredField("_commandBar").GetValue(ContainerElement) as CommandBar;
               var commandBarContent = ((Border)_commandBar.Content);

               var image = new Windows.UI.Xaml.Controls.Image();
               image.Source = new BitmapImage(new Uri("ms-appx:///Assets/xamarin-logo.png"));

               commandBarContent.Child = image;
          }
     }
}

Creamosun control Image y lo añadimos en la CommandBar.

El resultado en Android:

Logo en Android

En iOS:

Logo en iOS

Y en Windows:

Logo en UWP

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

¿One more thing?

¿Podemos personalizar aún más la barra de navegación?. Por supuesto!. Podemos tener necesidades como:

  • Personalizar el icono de navegación atrás.
  • Personalizar el texto asociado al botón para navegar atrás.
  • Insertar elementos como barras de búsqueda.
  • Etc.

Este conjunto de cambios, los veremos en otro artículo.

Recuerda que cualquier comentario o pregunta es bienvenido en los comentarios del artículo!.

Más información

[Xamarin.Forms] Localización de Aplicaciones

Introducción

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

¿Qué podemos hacer?

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

Globalización en Apps Xamarin.Forms

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

Archivos RESX

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

Creando los archivos RESX

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

Nuevo archivo de recursos

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

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

Añadiendo soporte al idioma español:

Español

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

RESX

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

Utilizando los archivos desde C#

Tras añadir un valor:

Añadimos valores

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

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

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

Y ahora desde XAML

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

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

     public string Text { get; set; }

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

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

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

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

¿Y cómo lo utilizamos?.

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

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

Y a continuación:

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

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

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

Otros recursos

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

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

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

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

Android

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

Imágenes localizadas en Android

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

iOS

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

Localización de imágenes en iOS

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

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

UWP

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

Localización de imágenes en UWP

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

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

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

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

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

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

Inglés

Y en español:

Español

Puedes descargar el código del ejemplo desde GitHub:

Ver GitHub

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

Recuerda, cualquier duda o comentario es bienvenido!

Más información

[Xamarin] Reporte de errores y analíticas con Mobile Center

Introducción

Una vez desarrollada una aplicación, recibir feedback sobre la misma es sumamente útil. Sería interesante saber información como cuanto tiempo estan los usuarios con nuestra aplicación, por que páginas navega, por donde nunca pasa el usuario, desde que dispositivos accede, errores, etc. Con esta información podemos sacar muchas conclusiones como detectar problemas de UX, añadir un mayor esfuerzo en zonas de la aplicación donde el usuario pasa la mayor parte del tiempo, esforzarnos por facilitar y promover el acceso a zonas apenas accedidas, corrección de bugs, etc.

¿Cómo podemos obtener toda esa información?

En este artículo, vamos a ver paso a paso como integrar una aplicación Xamarin.Forms con Visual Studio Mobile Center con el objetivo de descubrir y analizar errores en nuestras aplicaciones.

Mobile Center

Mobile Center es un centro de control de nuestras aplicaciones móviles. Mobile Center soporta tanto aplicaciones Xamarin como aplicaciones nativas con Java, Objective-C o Swift además de aplicaciones React Native.

Incluye:

  • Build: Integración y entrega continua.
  • Test: Pruebas automáticas en dispositivos reales.
  • Reporte de errores: Analíticas de errores en tiempo real.
  • Distribución: Despliegue a usuarios o grupos.
  • Analíticas: Métricas de uso.
  • Autenticación: Integración sencilla con métodos comunes.
  • EasyTables: Crear almacenamiento de forma muy rápida.

Crear la App

Accedemos al portal de Mobile Center.

Mobile Center

NOTA: Si no tienes cuenta de Mobile Center, puedes crear una desde este enlace.

Tras acceder al portal, debemos crear una nueva aplicación.

Nueva App

Se debe introducir el nombre de la aplicación, elegir Xamarin como plataforma y sistema operativo (iOS, Android o Windows). En este ejemplo, seleccionaremos Android.

Tras crear la aplicación, tenemos información disponible de que hacer para integrar Mobile Center en nuestra aplicación.

Pasos a realizar

Vamos a ello!

Preparar la App

Debemos gestionar los paquetes NuGet a nivel de solución (todos los proyectos que componen la solución Xamarin) para añadir las siguientes referencias:

  • Microsoft.Azure.Mobile.Analytics
  • Microsoft.Azure.Mobile.Crashes
Microsoft.Azure.Mobile.Analytics
Microsoft.Azure.Mobile.Crashes

Es hora de inicializar las analíticas. En la librería portable (PCL), en el archivo App.xaml.cs se deben añadir los siguientes using:

using Microsoft.Azure.Mobile;
using Microsoft.Azure.Mobile.Analytics;
using Microsoft.Azure.Mobile.Crashes;

A continuación, en el método OnStart que se lanzará al iniciar la App:

MobileCenter.Start("android={Insert App secret here}", typeof(Analytics), typeof(Crashes));

El código anterior, inicializa Mobile Center SDK en Android. Sin embargo, estamos en una aplicación Xamarin.Forms, ¿qué courre con el resto de plataformas?. Podemos inicializar más de una plataforma especificándola:

MobileCenter.Start("ios={Insert iOS App secret here};uwp={Insert UWP App secret here};android={Insert Android App secret here}", typeof(Analytics), typeof(Crashes));

Llegados a este punto, recibiremos analíticas en el portal como:

  • Actividad de usuarios
  • Sesiones por usuario diarias
  • Durración de cada sesión
  • Dispositivos más usados
  • Países
  • Actividad por versiones
  • Errores

Vamos a ver a continuación como registrar eventos personalizados o excepciones. Creamos una interfaz muy simple que nos permita hacer pruebas de registro de eventos y excepciones:

Nuestra App

Para registrar eventos personalizados utilizamos el método TrackEvent. Además de pasar una cadena con el nombre del evento, podemos utilizar un Dictionary para capturar diferentes parámetros.

var properties = new Dictionary<string, string>
{
     { "Parameter", "Data" }
};

Analytics.TrackEvent("EventBtn Clicked", properties);

¿Y qué debemos hacer para registrar excepciones?. En el arranque de la App inicializamos ya el registro de excepciones, no se requiere nada más. Si lanzamos con el segundo botón una excepción:

 throw new Exception("Exception information");

Quedará registrada.

Revisar analíticas

Accediendo al portal de Mobile Center, y entrando en nuestra App, tendremos acceso a analíticas desde los apartados Crashes y Analytics.

En Analytics tendremos información como:

  • Usuarios activos.
  • Sesiones diarias.
  • Duración decada sesión.
  • Dispositivos.
  • Versiones del sistema operativo.
  • Países.
  • Lenguajes.
Analíticas

Dentro del apartado de Analytics encontramos los eventos personalizados en Events:

Eventos

De nuevo tenemos información detallada como:

  • Listado de eventos.
  • Total de veces que se lanza el evento por usuario o por sesión.
  • Listado de parámetros.

Y llegamos a uno de los apartados más interesantes, Crashes:

Errores

Tenemos la traza del error detallado además de información extra que nos puede ayudar bastante como el dispositivo o versión del sistema operativo.

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

Ver GitHub

Más información

[Evento SVQXDG] De Java y Objective-C a .NET y de .NET a Java y Objective-C!

Introducción

Vuelta tras el verano!. Y como no podía ser de otra forma, volvemos con fuerza con un nuevo evento. Tras comenzar a conocer Xamarin algunas de las preguntas más habituales en muchos de los desarrolladores es:

  • ¿Puedo utilizar X librería Java en mi proyecto Xamarin?.
  • ¿Puedo utilizar X librería Objective-C en mi proyecto Xamarin?, ¿Y Swift?.
  • ¿Y al contrario?, ¿puedo utilizar mi librería .NET en un proyecto nativo?.

Para resolver a todas estas preguntas, desde SVQXDG, grupo de desarrolladores Xamarin de Sevilla, se organiza un evento donde veremos tanto las librerías de Bindings como Embedinnator-4000.

El evento

La agenda:

  • 19:00h – 20:00h: Librerías de Bindings: Tanto las comunidades de desarrolladores Android como de iOS cuentan con una gran variedad de fantásticas librerías nativas, desarrolladas en Java y Objective-C. ¿Cómo podemos utilizarlas desde nuestros desarrollos Xamarin?. En esta sesión veremos todo lo necesario para crear librerías de Bindings tanto en Android como en iOS. Con Juan María Lao.
  • 20:00h – 20:30h:  Embeddinator-4000: ¿Y si quiero utilizar mi fantástica librería .NET usada en Xamarin en desarrollos nativos?. Aquí es donde entra en juego Emveddinator-4000, una herramienta que permite generar librerías nativas tanto para iOS como para Android desde una librería .NET. Con Javier Suárez.

El lugar

El evento se celebrará en la ETS de Ingeniería Informática. Dirección detallada:

E.T.S. Ingeniería Informática – Universidad de Sevilla, Aula B1.32
Av. Reina Mercedes s/n
Sevilla Se 41012

La fecha

Será el próximo Jueves , 14 de Septiembre a las 19:00h (GMT+1).

¿Te apuntas?

Más información

Probando Embeddinator-4000: De .NET a iOS, Android o macOS nativo

Introducción

Los desarrolladores de Xamarin conocen las librerías de Bindings y la herramienta Sharpie. Hablamos de una herramienta de línea de comandos que permite automatizar la creación de una librería .NET que exponga las capacidades de la librería nativa. Sin embargo, existen una enorme cantidad de grandes librerías .NET utilizadas en Xamarin, ¿cómo podemos permitir utilizar librerías .NET desde una aplicación iOS o Android nativa?. En ocasiones arrancamos Apps Xamarin que tienen una base nativa o incluso hacemos una segunda App en Xamarin cuando ya existe una primera con código nativo. ¿No sería fantástico poder reutilizar?.

¿Qué es Embeddinator-4000?

Embeddinator-4000 es una herramienta de línea de comandos que permite convertir librerías  .NET a librerías que pueden ser consumidas por otros lenguajes.

Embeddinator-4000

La herramienta toma una librería .NET y genera los enlaces necesarios para exponer la librería .NET como una librería nativa. El gran objetivo es permitir utilizar librerías .NET en otras plataformas con código y herramientas nativas. Sigue en desarrollo y por lo tanto, se siguen añadiendo más y más funcionalidad, actualmente permite convertir de .NET a C, C++, Objective-C (plataformas Apple) y Java (Android principalmente).

La librería .NET

Si la herramienta convierte una librería .NET a una librería nativa, necesitamos una librería .NET a utilizar, ¿no?. Vamos a utilizar una sencilla librería .NET que permite consumir la API Rest Netflix Roulette (permite obtener una película recomendada aleatoria de Netflix en base a una serie de filtros como la puntuación por ejemplo).

La librería desarrollada con código C# hace peticiones HTTP a la API utilizando HttpWebRequest y deserializamos Json con DataContractJsonSerializer.

Obtener información de películas

Utilizando Embeddinator

Llega el momento de utilizar la herramienta. Pero antes de ello, repasemos los requisitos.

Para convertir a librería Objective-C:

  • macOS 10.12 (Sierra) o superior.
  • Xcode 8.3.2 o superior.
  • Mono 5.0.

Para convertir  librería Java:

  • Java 1.8 o superior.
  • Mono 5.0.
  • Xcode 8.3.2 (en MacOS) .
  • Visual Studio 2017 con SDK de Windows 10 (en Windows).

Vamos a convertir nuestra librería .NET a un .framework, librería nativa para utilizar en un proyecto nativo iOS en XCode. Comenzamos con la instalación de la herramienta. Existen dos opciones:

  • Utilizar un paquete ya preparado listo para la instalación.
  • Sincronizar el repositorio y compilar.

La instalación del paquete es sencilla y la habitual:

Instalar la herramienta

Una vez instalada la herramienta, accediendo a un terminal tendremos acceso a la herramienta de línea de comandos con objcgen.

NOTA: La ruta absoluta de la herramienta es /Library/Frameworks/Xamarin.Embeddinator-4000.framework/Commands/objcgen.

Objective-C es un lenguaje utilizado en macOS, iOS, tvOS y watchOS. La herramienta soporta todas las plataformas aunque hay diferencias en el uso.

Vamos a centrarnos en iOS. Para utilizar la herramienta contamos con una serie de parámetros:

  • Platform: Plataforma destino. Posibles valores: android, windows, macos, ios, watchos, tvos.
  • Outdir: Directorio donde vamos a obtener el resultado.

objcgen Library.dll –target=framework –platform=iOS –outdir=output -c –debug

Utilizando la herramienta

Tendremos información de cada acción realizada:

Feedback

Al concluir la herramienta,  en la carpeta de salida (en nuestro ejemplo, se ha creado una carpeta llamada output) veremos lo siguiente:

La salida

Tenemos varios ficheros interesantes, a destacar:

  • .framework: Librería nativa preparada para utilizar en desarrollo nativo.
  • binddings: Código Objective-C con nuestra librería.

Probando el resultado

Creamos un nuevo proyecto iOS desde XCode:

Nuevo proyecto

Arrastramos el .framework al proyecto:

Copiar .framework

En las propiedades del proyecto, añadimos el framework recién copiado como Embedded Binaries:

Embedded Binaries

Creamos una interfaz de usuario sencilla pero suficiente para poder probar que todo funciona como esperamos:

La interfaz de usuario

Una caja de texto donde el usuario puede introducir el nombre de una película, un botón para hacer la búsqueda y algunso textos donde mostrarle información como la descripción, la puntuación, etc.

Al pulsar el botón debemos hacer la búsqueda. En el controlador:

- (IBAction)findMovie:(id)sender {
     NetflixRoulette_NetflixRouletteFetcher * fetcher = [[NetflixRoulette_NetflixRouletteFetcher alloc] initWithMovie: _movieTxtField.text];
 
     NetflixRoulette_NetflixRouletteResult * result = [fetcher getMovie];
 
     if (result) {
          _titleLabel.text = [result showTitle];
          _yearLabel.text = [result releaseYear];
          _ratingLabel.text = [result rating];
          _descLabel.text = [result summary];
     }
}

Trabajamos con la librería .NET pero utilizando en este caso código Objective-C.

El resultado:

 

El resultado

Limitaciones

Mencionamos previamente que estamos ante una herramienta en desarrollo y cuenta con por supuesto algunas limitaciones:

  • No podemos utilizar dos librerías generadas con la herramienta en la misma aplicación.
  • Debido a la falta de metadatos en .NET para la gestión de nulidad, se generan NS_ASSUME_NONNULL_BEGIN.
  • Igualmente carece en estos momentos de soporte a tipos genéricos.
  • El soporte a Apple Watch está en desarrollo.
  • Etc.

Conclusiones

El pasado //BUILD fue un momento repleto de grandes anuncios. Entre ellos, uno de mis favoritos (y la herramienta estaba ya disponible en GitHub previamente) fue el anuncio y demostración de esta herramienta, Embeddinator-4000. Viene a cubrir una necesidad real que me he encontrado en más de una ocasión. Poder reutilizar código .NET en plataformas nativas al igual que ya se podía utilizar código nativo en Xamarin rompe una barrera en determinados momentos. Con mejoras en esta herramienta, Xamarin Live Player, Xamarin.Forms llegando a más plataformas (Linux incluido) o Forms Embedding el futuro a corto plazo se ve muy emocionante. Y a ti, ¿que te parece?.

Más información

[Material] Xamarin Dev Days Málaga 2017

El evento

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

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

El material

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

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

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

Ver GitHub

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

Más información

[Material] Xamarin Dev Days Madrid 2017

El evento

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

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

El material

Pude participar en el evento con una de las sesiones. Nos centramos en el desarrollo de aplicaciones móviles multiplataforma utilizando Xamarin.Forms:

Comenzamos por una introducción de conceptos básicos, primera demo  y terminamos repasando todas las últimas novedades como Forms Embedding o las futuras novedades de Xamarin.Forms 3.0.

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

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

Ver GitHub

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

Más información

[Evento Online] Introducción a Xamarin.IoT

El evento

¿Tienes pensado instalar o tienes instalado Linux en tu dispositivo IoT, usando la flexibilidad y potencia de C#?.

Xamarin IoT permite a los desarrolladores crear aplicaciones de consola, usando IDE’s de primer nivel como son Visual Studio, Visual Studio for Mac/Linux, además de poder ejecutar y debuguear tu aplicación favorita de un dispositivo remoto.

Una charla intensa, donde si el tiempo nos lo permite, intentaremos abordar todos los puntos:

  • Presentación e introducción Xamarin.IoT.
  • Setup de dispositivo.
  • Arquitectura.
  • Ejemplos de uso.
  • Hermes.Mqtt Client/Server.
  • Azure Sql Database + Entity framework.
  • IoTSharp.Components.

A todos estos puntos y a todas las preguntas posibles intentaremos dar solución en esta sesión online!.

El ponente

Tendré la compañía en esta ocasión de Jose Medrano. Jose es desarrollador de software y actualmente trabaja en Xamarin formando parte del equipo de Xamarin Profiler & Xamarin IoT.

Jose Medrano

La fecha

El evento será el próximo Martes, 27 de Junio.

  • 19:00 en España
  • 13:00 en Colombia
  • 12:00 en México Centro
  • 13:30 en Venezuela
  • 15:00 en Chile continental

¿Te apuntas?

Más información