Visual Studio 15 Preview side by side Visual Studio 2015 Update 2. ¡Es un hecho!

image

Hoy es el día,

¿Convivirá Visual Studio 15 Preview y Visual Studio 2015 Update 2? Al igual que  en versiones anteriores, la convivencia no debería ser un problema, si bien, siempre queda alguna duda.  Así pues, si quieres saber cual es la respuesta, continua leyendo.

Antes de nada, echemos un vistazo a las novedades que incluye cada versión:

– Visual Studio 2015 Update 2: Página oficial

– Visual Studio 15 Preview (VS15): Página oficial o bien, en este otro link.

 

VS15 trae muchas novedades interesantes, pero lo que realmente ha hecho que no me lo piense mucho más, es que el desarrollo para Xamarin (Android e iOS) ya se encuentra incorporado de manera nativa en el IDE.

image

 

El proceso de instalación que he sido es el siguiente:

1) [Opcional]. Como paso inicial pero no necesario, he aprovechado para actualizar previamente el sistema con la versión “Windows 10 Insider Preview 14295  New!” sin problema de ningún tipo, no al menos hasta el momento. ¡Recordemos que se trata de una preview y todo lo que ello conlleva!

 

2) Actualización de Visual Studio 2015 al Update 2. Completada la instalación, me encuentro con que no puedo ejecutar mis desarrollos UWP. Concretamente, debido al error: “Cannot register the f756c511-bf45-40c2-b968-06659f6042e7_1.0.8.0_x86__8vrdchia8f0ps package because the following error was encountered while registering the windows.defaultPrograms ”.

Tras mucho “googlear” y poco resultado, finalmente revisando y analizando el fichero “Package.AppxManifest” encontré que todo se reducía a la eliminación de la siguiente entrada.

<uap:Extension Category=”windows.appointmentsProvider” EntryPoint=”ElGuerre.App1.Uwp.App”>
<uap:AppointmentsProvider />
</uap:Extension>

 

NOTA. Tras la eliminación manual de esta entrada, podemos volver añadirla desde el editor del manifiesto. Si tras ello editamos nuevamente el fichero, no la volveremos a encontrarla, pero seguirá apareciendo marcada en el editor. ¡Supongo que ha debido quedar obsoleta!

3) Instalación de VS15. Finalizada esta instalación podemos encontrarnos con algunos errores, quizás un poco habituales:

  1. Si se produce el error “[Critical] XDE Exit Code: InvalidArguments (3)”,
    • Reparar el SDK de Android (“Programs and Features > Microsoft Visual Studio Emulator for Android > Change”  y Reparar (“Repair”)
    • Eliminar de Hiper-V Manager todos los “Virtual Switch Manger”
    • Ejecutar XdeCleanup.exe “C:\Program Files (x86)\Microsoft XDE\10.0.10240.0”.
  2. Si seguimos teniendo el mismo problema, pasamos a modo manual:
    • Usar la herramienta “Visual Studio Emulator for Android” para desinstlar, descargar o iniciar emuladores. ¡Aseguremos de “matar” los procesos XDE si la desinstalación, descarga o inicio de un emulador nos advierte de no poder completar la acción!
    • Utilizar la consola de Hyper-V para ver el detalle de arranque y comporobar con más detalle lo que pueda estar ocurriendo.
    • Probar a deshabilitar la wifi y repetir el paso (2) conectados por cable a la red.
    • Si llegado este punto, los emuladores no se inician y observamos el error “init: Failed to read from /dev/hw_random: No such device”, entonces, eliminamos del fichero “C:\Program Files (x86)\Microsoft XDE\10.0.10240.0\SKUs\Android\xdesku.xml”, la entrada: GuestDisplayProvider=”VsEmulator.OpenGLGuestDisplay”.

Conclusión

  • Completadas las actualizaciones anteriores y, habiendo realizado el tour completo de errores y pasos, puedo concluir que una vez resueltos los conflictos, ambos productos pueden convivir.

Espero que este post sirva para despejar alguna que otra incertidumbre y para animar a alguien a dar el paso. Winking smile

Saludos
Juanlu

Referencias:

TIP: Error de ejecución en Windows 10 Mobile pero NO en Windows escritorio. Y únicamente en modo Debug

Resultado de imagen de

Muy buenas,

No podía pasar por alto compartir este error y su solución con vosotros después de haberme vuelto loco durante las últimas horas. La cuestión es que por alguna razón que desconozco tras actualizar Visual Studio Update 2, me he topado con este error: “Exception thrown: ‘System.Reflection.TargetInvocationException’ in mscorlib.ni.dll

Error, que puede desenvocar en los los siguientes:

1) Inner Exception:

at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
   at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
   at System.Delegate.DynamicInvokeImpl(Object[] args)
   at GalaSoft.MvvmLight.Ioc.SimpleIoc.DoGetService(Type serviceType, String key, Boolean cache)
   at GalaSoft.MvvmLight.Ioc.SimpleIoc.MakeInstance[TClass]()

2) Failed to assign to property ‘Windows.UI.Xaml.FrameworkElement.DataContext’

Lo que me parece más curioso es que sólo afecta a la aplicacion Windows 10 Mobile cuando es ejecutada en modo DEBUG. No ocurre así cuando es ejecutada en modo release ni en ninguno de los modos cuando es ejecutada como Windows10 Escritorio. El error se produce al intentar resolver la dependencia de una de las clases que hace uso de SQLite.

Solución:
Tras dar algunas idas y venidas revisando los últimos cambios realizados en el código, creo que el error puede deberse al uso con SQLLite, el cual parece hacer uso de la dependencia “Visual C++ 2015 Runtime for Universal Windows Platform Apps“. Tras añadir la misma el error deja de producirse y la aplicación vuelve a funcionar.

La pregunta que me sigo haciendo es ¿Por qué solo se produce este error para una circunstancia muy particular? ¿Quizas es un Bug? …

Espero que este pequeño TIP, sirva para evitar algun que otro dolor de cabeza.

Saludos y buen fin de semana
Juanlu

Template 10. Un nuevo camino para hacer aplicaciones UWP. ¡La churrera está en marcha!

Icon for package Template10

 

Template 10: “Una amplia biblioteca de helpers, servicios y clases para las aplicaciones UWP con el fin de maximizar la genialidad y minimizar lo repetitivo.”

UWP se encuentra entre nosotros y como sabemos, viene con muchas y nuevas caráctericticas: Device families, SplitView/Hamburger Menu, Relative Panel, StateTriggers/AdaptiveTrigger, etc. Si bien es cierto, la creacion de aplicaciones siguie siendo repetitiva teniendo que crear la estructura base o esqueleto de nuestra aplicación cada vez que comenzamos con una nueva. Es cierto que podemos crear una plantilla y guardarla para partir de ella. También es cierto que seguiremos teniendo que repetir código o pensar en la navegación, etc. Para evitar “todo” esto ¡llega TEMPLATE 10!

 

Con Template10 podremos comenzar a crear aplicaciones como UWP “churros” y disponer en ellas desde el momento cero, de “todo” lo imprescindible.

  • Controles
  • Behaviors
  • Services
  • Converters
  • MVVM y, ademas compatible con con MVVMLight (Galasoft) y IoC/DI.
  • etc.

Personalmente lo que mas me gusta de Template 10, es toda la funcionalidad que incluye Out-of-the-box:

  • Plantillas de projectos “Mínima” o “Básica”
  • BootStrapper. Menos eventos en nuestro “App.xaml.cs”. En lugar de: OnLaunched, OnActivated, OnFileActivated, OnCachedFileUpdaterActivated, etc. Unicamente necesitaremos dos: “ public override async Task OnInitializeAsync(IActivatedEventArgs args)” y “   public override async Task OnStartAsync(StartKind startKind, IActivatedEventArgs args)”
  • Menu Hamburger
  • Navegación y control del Menú, sin tener que codificar practicamente nada.
  • Adios a “Behaviors SDH (XAML)”. Referencia con advertendia de obsolescencia y Bienvenida: “Microsoft.Xaml.Behaviors.Uwp.Managed”, como Open Source.
  • El códio se encuentra en Gihub: https://github.com/Windows-XAML/Template10 y además, con algunos ejemplos para hacerlo aún más fácil.

Los pasos que seguiremos para la ejecución de una aplicación haciendo uso de Templat10 son los siguientes:

  1. Descargar el instalable (v1.5) para la plantilla de proyecto desde aquí.
  2. Crear un nuevo proyecto de tipo Template 10 (Minimal): image
  3. Actualizar desde NuGet el paquete “Template10” e incluir el paquete “Microsoft.Xaml.Behaviors.Uwp.Managed”
  4. En estos momento y hasta que sea actualizada la plantilla de proyecto, modificar el constructor de la clase Shell.xaml.cs, sustituyendo el parámetro “NavigationService” por su interfaz “INavigationService” tal y como puede verse en la siguiente image: image
  5. F5 y voilá…

imageimage image

Espero que haya sido de utilidad.

Gracias @jerrynixon, por ofrecernos tan apreciado trabajo y hacernos más facil la creación de nuestas aplicaciones. “Thanks so much”

Saludos
Juanlu, ElGuerre

[TIP Windows10 – x:Bind]: El fin de los parámetros <null> desde un “AppBarButton”

Muy buenas,

Al desarrollar aplicaciones Window Phone 8/8.1 con MVVM, el paso de parámetros desde un boton “AppBarButton” de una “CommandBar”,  siempre se convertía en una tarea un poco tediosa y, por consiguiente, teníamos que implementar algun tratamiento extra . Ahora, para las aplicaciones universales y gracias a “x:Bind”, ya no será necesario. Veamos como solucionarlo con un pequeño cambio.

Situación / Problema:

Enlazando el DataContext de una página con su ViewModel. Aunque podría se cualquiera, en en este ejemplo usamos el Contenedor de Unity (SimpleIoC) de MvvmLight de Galasoft.

image

A continuación, el enlazado con el “Comand” se hace a partir del DataContext antes definido. Sin embargo y, aunque debería ocurrir lo mismos para el “CommandParameter”, no es así. En el Comando (RelayCommand) implementado en el ViewModel, el parámetro recibido siempre tiene el valor nulo en lugar del objeto pasado “lvTasks” (ListView, en este caso).

image

Nota: Esto ocurre ya que el botón no es capaz de localizar el elemento “lvTasks” definido en la página, puesto que el enlazado lo está haciendo con el ViewModel. ¡Lo curioso es que esto es así para este tipo de botones pero nó para otros controles!

 

Solución:

Hasta ahora, para solucionar los inconvenientes anteriores, teníamos que crear una propiedad adicional en el ViewModel, cambiar los DataContext en el “CodeBehind” y otras alternativas que podemos ver buscando en Google. La cuestión es que supone ensuciar el código para hacer algo muy trivial.

Desde que contamos con “x:Bind” en Windows 10, la solucion es mucho más simple. Cambiamos “Binding ElementName=lvTasks” por “x:Bind lvTasks” y todo solucionado:

image

Como vemos, x:bind es capaz de localizar el control “lvTasks” en la página y por tanto nuestro RelayCommand deja de recibir parámetros nulo.

image 

Recordemos además, que el uso de x:Bind crea un enlace a datos fuertemente tipados y aporta mejoras de rendimiento.  Para más informació sobre x:Bind, podéis echar un vistazo a este post de nuestro compañero @JavierSuarezRuiz

Espero que sea util.

Salduos
Juanlu, ElGuerre

[TIP] Mejorando Linq. Un Comparador genérico y un “DistinctBy” !!!

Intentando utilizar el “Distinct” de Linq, me he encontrado con la necesidad te tener que implementar una clase “IEqualityComparer<T>”. Concretamente tenía que obtener objetos distintos en una gran lista. Así que, después de buscar un poco, y gracias a nuestro compañero driis, me gustaría compartir la siguiente clase con vosotros. Creo que puede comenzar a formar parte de nuestros desarrollos.

La utilizaremos de cualquier de las dos formas siguientes:

  1. list.DistinctBy(item => item.Id);
    1 public static class Compare 2 { 3 public static IEnumerable<T> DistinctBy<T, TIdentity>(this IEnumerable<T> source, Func<T, TIdentity> identitySelector) 4 { 5 return source.Distinct(Compare.By(identitySelector)); 6 7 public static IEqualityComparer<TSource> By<TSource, TIdentity>(Func<TSource, TIdentity> identitySelector) 8 { 9 return new DelegateComparer<TSource, TIdentity>(identitySelector); 10 } 11 12 private class DelegateComparer<T, TIdentity> : IEqualityComparer<T> 13 { 14 private readonly Func<T, TIdentity> identitySelector; 15 16 public DelegateComparer(Func<T, TIdentity> identitySelector) 17 { 18 this.identitySelector = identitySelector; 19 } 20 21 public bool Equals(T x, T y) 22 { 23 return Equals(identitySelector(x), identitySelector(y)); 24 } 25 26 public int GetHashCode(T obj) 27 { 28 return identitySelector(obj).GetHashCode(); 29 } 30 } 31 }

  2. list.Distinct(Compare.By(item => item.Id));

O incluso, cada vez que necesitemos un “IEqualityComparer<T>” como parámetro.

 

Saludos

Juanlu

TIP: Unos pocos clics para que tus tests de Windows Phone 8.1 funcionen en Visual Studio 2015

¿Estás trabajando en una aplicación Windows Phone 8.1 y quieres que tus tests funcionen en Visual Studio 2015 en unos pocos clics? Sólo tienes que seguir estos breves pasos:

Coded UI TestT

  • Simplemente cambia el siguiente “Reference Path”:

image

Unit Test

  • Selecciona el proyecto y desde su menú contextual elige la opción: “Unload Project”
  • Vuelve a seleccionar el proyecto, y edítalo: “Edit WindowsPhone.Tests (unavailable)”.
  • Busca las siguientes líneas y asegúrate de que la versión indicada en las mismas es la “14.0”.
  • Vuelve a cargar el proyecto  (“Reload project”) y listo para compilar sin errores.

Esto ha sido todo. ¡Ahora tienes un motivo menos para no tener instalado VS2015!
Saludos
Juanlu

WinRT: Working on Local Storage: File System & SQLite. IsolatedStorageExplorer, IsoStorySpy y SQLite Toolbox

Muy buenas,

Continuando con los posts pendientes, veamos en éste algunas herramientas imprescindibles al desarrollar aplicaciones Windows 8.1 / Windows Phone 8.1 y Windows 10.

Cuando trabajamos con ficheros, es decir, con el Storage, es muy normal querer conocer que información estamos guardando en ellos  y las rutas en las que estos son almacenados. También puede ser interesante intercambiar ficheros entre el PC y el emulador o dispositivo móvil y viceversa. 

De esta misma manera, también podemos plantearemos si utilizar o no ficheros para la gestión de la información de nuestra aplicación o bien, utilizar un sistema de BD. ¿SQL Server CE o SQLite? La respuestas a estas preguntas pueden variar y no pretendemos abordarlas aquí. ¡Podemos echar un vistazo a este enlace para ver una comparativa entre estos dos y aun otro!. Lo que si veremos ,es como trabajar con SQLite desde Visual Studio y como comprobar la información que almacenamos en el. Es decir, como realizar consultas utilizando una interfaz gráfica al igual que como hacemos con “Management Studio” para SQL Server.

Veamos cuales son algunas de las herramientas que va a permitirnos hacer todo esto:

1) FILE SYSTEM

a) IsolatedStorageExplorer. Esta herramienta, como comentamos en el capítulo uno, es una de las herramientas que encontramos en el directorio de instalación del SDK (“C:Program Files (x86)Microsoft SDKsWindows Phonev8.1Tools”) y se basa en línea de comandos. Su uso es el siguiente:

1. Listado de emuladores instalados. “ISETool.exe EnumerateDevices”:

IsolatedStorageExplorer-EnumerateDevices

2. Listado de ficheros para el dispositivo conectado. “ISETool.exe dir deviceindex:2 300177dd-2599-41dd-acf4-3168c460f615”.

Dónde: deviceindex:2 hace referencia al dispositivo/emulador del que queremos listar los ficheros, según la lista anterior, es decir, “Emulator 10.0.1.0 WVGA 4 inch 1GB”. El identificador (o GUID) de la aplicación se encuentra en el fichero de manifiesto de la aplicación indicado por la propiedad “PhoneProductId”.

3. Copia de ficheros entre dispositivo/emulador y el PC. “ISETool.exe <ts | rs> deviceindex:2 300177dd-2599-41dd-acf4-3168c460f615 "C:MyAppFiles”.

Dónde: ts copia los ficheros desde el dispositivo al PC creando siempre el subdirectorio “IsolatedStore” en caso de no existir y, reemplaza todo su contenido sin previo aviso. rs, copia los ficheros desde la ruta del PC al dispositivo.

Para mayor detalle sobre esta herramienta visitar la página: https://msdn.microsoft.com/es-es/library/windows/apps/dn629254.aspx

b) IsoStorySpy:  Ésta, además de realizar las mismas acciones que “IsolatedStorageExplorer”, dispone de una interfaz gráfica que hace más fácil e intuitivo su uso. Podemos descargarla desde Codeplex (https://isostorespy.codeplex.com).

 image

Nota: Los número en la imagen anterior indican los pasos a seguir para la gestión ficheros.

Además, esta herramienta, añade más funcionalidad. Por ejemplo, incluye una zona de “Preview”, para la visualización de los ficheros de tipo imagen, vídeo o incluso tablas de “SQL Server CE” etc.

2) BD – SQLite

SQLite es un sistema gestor de base de datos relacional, Atómico, Consistente, Integro, permite el Aislamiento de sus transacciones y Durable (o persistente), es decir, es “ACID Compliant”. Se trata de una única librería con un tamaño relativamente pequeño, unos 275kb y guarda la información de BD (tablas, índices, datos, etc.), en un sólo fichero.

Es multiplataforma y de dominio público y gracias a su pequeño tamaño es recomendado y está siendo muy usado en aplicaciones móviles: Windows Phone 8/8.1, Windows 10, Android, BlackBerry, Google Chrome, iOS, Maemo, Symbian, webOS, etc.

Actualmente, SQLite es utilizado por muchas aplicaciones conocidas: Skype, Adobe Photoshop Elements, Mozilla Firefox, Opera, OpenOfficel.org, Apple Mail, etc.

El sitio web oficial es http://sqlite.org y la descarga podemos realizarla desde este otro enlace: http://sqlite.org/download.html .

INSTALACIÓN: Para poder trabajar con SQLite desde Visual Studio seguiremos los siguientes pasos:

1. Descargar el paquete “.VSIX”, desde la página de descargas antes indicada, Windows Runtime 8.1, Windows Phone 8.1 o Windows 10 / 10 Móvil. ¡Por el momento podemos optar por los paquetes de 8.1, que funcionan correctamente en todos los casos!

2. Instalar el paquete “.VSIX” y reiniciar Visual Studio si estaba abierto. Para comprobar la instalación, desde el menú “Tools – Extensions and Updates…” de Visual Studio, acceder a las extensiones y actualizaciones instaladas:

ExtensionAndUpdates-SQLite

3. Añadir al proyecto Windows / Windows Phone la referencia “SQLite for Windows 8.1” / “SQLite for Windows Phone 8.1” respectivamente tal y como puede verse en la siguiente imagen para un proyecto de Windows Phone 8.1:

AddSQLiteReferenceToProject

Nota: Únicamente es posible añadir la extensión anterior a los proyectos “Windows” y/o “Windows Phone”.

4. Para el proyecto “Windows” / “Windows Phone” y para nuestro proyecto de acceso a datos,  por ejemplo, “Data” , instalar desde NuGet, el paquete: “SQLite.Net.Async-PCL”, que a su vez instalará la dependencia “SQLite.Net-PCL” y junto a esta, se añadirá también, automáticamente la dependencia “SQLite.Net.Platform.WinRT”.

AddSQLiteNugetToProject

A partir de este momento, nuestro proyecto “Data” está listo para gestionar la información de la aplicación con SQLite.

USO: Aunque no entraremos en el detalle de como usar SQLite, no al menos en este post, me gustaría incluir como hacer la conexión y la creación de tablas (mediante código), para comenzar a trabajar.

1 public SQLiteProvider(ISQLitePlatform sqlitePlatform) 2 { 3 this.sqlitePlatform = sqlitePlatform; 4 5 if (null == db) 6 { 7 // Obtener ruta donde generar el fichero ".sqlite" que contendrá la estructura de la BD y su contenido. 8 string databaseFile = StorageHelper.GetFullPath(DATABASE_FILE_NAME); 9 if (!string.IsNullOrWhiteSpace(databaseFile)) 10 { 11 var connection = new SQLiteConnectionWithLock(sqlitePlatform, new SQLiteConnectionString(databaseFile, false)); 12 db = new SQLiteAsyncConnection(() => connection); 13 14 InitializeAsync(); 15 } 16 else 17 { 18 throw new System.IO.IOException("No access to database file"); 19 } 20 } 21 } 22 23 private async System.Threading.Tasks.Task InitializeAsync() 24 { 25 await db.CreateTableAsync<ProjectTable>(); 26 await db.CreateTableAsync<TaskTable>(); 27 await db.CreateTableAsync<ImageTable>(); 28 }

Una vez generada la conexión de la BBDD y la creación de las tres tablas anteriores, encontraremos un fichero “MyProjects.sqlite” en la carpeta “Local”. Copiaremos éste al PC usando las herramientas (“IsolatedStorageExplorer” o “IsoStoreSpy”).

Herramienta gráfica (SQL Server Compact / SQLite Toolbox):

De la misma forma que al trabajar con ficheros necesitábamos conocer la información almacenada en ellos, querremos conocer también la información con la que trabajamos en SQLite, que consultas estamos ejecutando y que información estamos obteniendo, insertando, actualizando o eliminando.

Los pasos a seguir para trabajar con ella son:

1. Ejecutar el instalable “SqlCeToolbox.vsix” y reiniciar Visual Studio.

2. Ejecutar la opción de menú: “Tools – SQL Server Compact/SQLite Toolbox”, para mostrar el explorador o caja de herramientas.

Menu-SQLite Toolbox

3. Una vez que aparezca el explorador, crear una nueva conexión indicando un nombre de fichero para la misma, o bien seleccionar un fichero existente con extensión “.sqlite”. En nuestro caso, seleccionaremos el fichero “MyProjects.sqlite”, generado a partir del código anterior y que hemos descargado al PC.

AddSQLite_Connection

4. Una vez creada la conexión, el explorador de SQLite, estaremos en disposición de comenzar a trabajar con la BD al igual que lo hacemos para SQL Server con Visual Studio o con “Management Studio”.

SQLToolbox in action

A partir de aquí, a seguir explorando y trabajando con estas herramientas, ¡que no se diga que las aplicaciones WinRT o Windows 10 suponen un contratiempo! Guiño

Espero que sea de utilidad.

Saludos

Juanlu

Mejorando la “responsividad”: asyc y await, AsyncLazy<T> y MVVM asíncrono (NotifyTaskCompletion<T>)

Muy buenas,

Hoy me gustaría comentar algunos “Tips“ que creo, deberíamos conocer cuando desarrollamos aplicaciones WinRT, o incluso cuando desarrollamos casi cualquier aplicación .NET, al menos 4.0 o superior.

En primer lugar, simplemente recordar / repasar el patrón async y await, que cada día cobra más y más importancia debido a los dispositivos móviles y a las aplicaciones responsivas.

  • Se trata de un patrón que hace más ágil la interacción con  nuestras aplicaciones evitando incluso alguna larga espera (“congelación”) de la misma durante su ejecución.
  • Permite el lanzamiento de operaciones asíncronas sin bloqueos en la Interfaz de Usuario
  • Su implementación es muy sencilla y mantiene un código legible.
  • Se introdujo a partir de NetFX 4.5 / Windows Phone 8 y WinRT.
  • Podemos utilizarlo en NETFX 4.0 / Windows Phone 7.1 / Silverlight 4 / MonoTouch / MonoDroid  y  Librerías de clases Portables, con Visual Studio 2012 o posterior  y el paguete NuGet, Microsoft.Bcl.Async.

Nota: Asyn no implica necesariamente el uso de hilos (threads), esto es opcional.

Los métodos de pruebas unitarias (Tests), tendrán que indicarse como “Public async Task TestMethod1() { … }”. ¡No sé porqué, pero siempre se me olvida. Y hasta que no pretendo lanzarlos y no los veo en la consola, no lo recuerdo! grrr…

 

En segundo lugar. Para retrasar la carga de un costoso consumo de recursos, hasta el momento en el que sea realmente necesario su uso. Utilizaremos el patrón de instanciación perezosa, o, mas comúnmente conocido como ” Lazy<T>. Sin embargo, si para dicha carga se requiere no bloquear la UI, necesitaremos por tanto, un método asíncrono, con lo que perderemos de vista el objetivo principal del patrón Lazy. Éste, explicitamente no lo permite.

Por ejemplo, si en WinRT queremos ejecutar la instrucción

this.folder = new Lazy<StorageFolder>(() => this.CreateFolderIfNotExistsAsync(folderName));

Podemos pensar  en cambiarla para que sea asíncrona:

this.folder = new Lazy<StorageFolder>(async () => this.CreateFolderIfNotExistsAsync(folderName));”

No obstante, obtendremos un error en tiempo de diseño: “Cannot convert lambda expression to type ‘System.Threading.LazyThreadSafeMode’ because it is not a delegate type.

Podemos seguir intentándolo, pero en lugar complicar el código, echemos un vistazo a la siguiente clase:

1 public class AsyncLazy<T> : Lazy<Task<T>> 2 { 3 public AsyncLazy(Func<T> valueFactory) : 4 base(() => Task.Factory.StartNew(valueFactory)) { } 5 6 public AsyncLazy(Func<Task<T>> taskFactory) : 7 base(() => Task.Factory.StartNew(() => taskFactory()).Unwrap()) { } 8 9 public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); } 10 }

Gracias a ella y de manera muy sencilla, nuestra instanciación perezosa se ejecutará de manera asíncrona y no bloqueará el UI.

this.folder = new AsyncLazy<StorageFolder>(() => this.CreateFolderIfNotExistsAsync(folderName));

 

Por último. Cuando trabajamos con WinRT siguiendo el patrón MVVM y queremos abrir una nueva ventana/pantalla, los enlaces a datos (o Bindings) se realizan de manera síncrona y automática en el momento de la carga de esa nueva ventana. Puede ocurrir por tanto, que el tiempo de espera se vea incrementado dando la sensación de una aplicación poco responsiva. Si además, la ventana es nuestro “Home”, el impacto en el usuario puede ser mayor. Para evitarlo, entre otras opciones, la clase “NotifyTaskCompletion<T>”, como la siguiente, puede ayudarnos:

1 public sealed class NotifyTaskCompletion<TResult> : INotifyPropertyChanged 2 { 3 public NotifyTaskCompletion(Task<TResult> task) 4 { 5 Task = task; 6 if (!task.IsCompleted) 7 { 8 var watcher = WatchTaskAsync(task); 9 } 10 } 11 private async Task WatchTaskAsync(Task task) 12 { 13 try 14 { 15 await task; 16 } 17 catch 18 { 19 } 20 21 var propertyChanged = PropertyChanged; 22 if (propertyChanged == null) return; 23 24 propertyChanged(this, new PropertyChangedEventArgs("Status")); 25 propertyChanged(this, new PropertyChangedEventArgs("IsCompleted")); 26 propertyChanged(this, new PropertyChangedEventArgs("IsNotCompleted")); 27 if (task.IsCanceled) 28 { 29 propertyChanged(this, new PropertyChangedEventArgs("IsCanceled")); 30 } 31 else if (task.IsFaulted) 32 { 33 propertyChanged(this, new PropertyChangedEventArgs("IsFaulted")); 34 propertyChanged(this, new PropertyChangedEventArgs("Exception")); 35 propertyChanged(this, new PropertyChangedEventArgs("InnerException")); 36 propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage")); 37 } 38 else 39 { 40 propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted")); 41 propertyChanged(this, new PropertyChangedEventArgs("Result")); 42 } 43 } 44 45 public Task<TResult> Task { get; private set; } 46 public TResult Result 47 { 48 get 49 { 50 return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(TResult); 51 } 52 } 53 public TaskStatus Status { get { return Task.Status; } } 54 public bool IsCompleted { get { return Task.IsCompleted; } } 55 public bool IsNotCompleted { get { return !Task.IsCompleted; } } 56 57 public bool IsSuccessfullyCompleted 58 { 59 get 60 { 61 return Task.Status == TaskStatus.RanToCompletion; 62 } 63 } 64 public bool IsCanceled { get { return Task.IsCanceled; } } 65 public bool IsFaulted { get { return Task.IsFaulted; } } 66 public AggregateException Exception { get { return Task.Exception; } } 67 public Exception InnerException 68 { 69 get 70 { 71 return (Exception == null) ? null : Exception.InnerException; 72 } 73 } 74 public string ErrorMessage 75 { 76 get 77 { 78 return (InnerException == null) ? null : InnerException.Message; 79 } 80 } 81 public event PropertyChangedEventHandler PropertyChanged; 82 }

La usaremos en uno de los métodos de inicio (o constructor),  de nuestra pantalla, de la siguiente manera:

this.MyListAsync = new NotifyTaskCompletion<Project>(this.service.GetProject(projectId));

En el XAML realizaremos el enlace con la instrucción: “MyListAsync.Result”:

1 <ListView Grid.Row="1" x:Name="listViewTasks" 2 Margin="10,0,5,0" 3 ItemsSource="{Binding MyListAsync.Result, Mode=TwoWay}" 4 ItemTemplate="{StaticResource SmallImageDetailTemplate}" >

Y, por ejemplo, habilitaremos o no un botón de edición sólo si la carga se ha realizado con éxito.

1 <Button Content="&#xE104;" Margin="10,0,0,0" 2 FontSize="18" FontFamily="Segoe Ui Symbol" 3 Visibility="{Binding ProjectAsync.IsSuccessfullyCompleted, Converter={StaticResource BooleanToVisibilityConverter}}" 4 Command="{Binding EditProjectCommand}" />

Así mismo y aunque algo menos elegante, podríamos interactuar en el ViewModel de la siguiente manera, o incluso exponiendo eventos tales como “NotifySuccessfullyCompleted, “NotifyFaulted” y “”NotifyCanceled” dentro de la clase NotifyTaskCompletion<T>.

1 this.ProjectAsync.PropertyChanged += (sender, e) => 2 { 3 if (e.PropertyName == "IsSuccessfullyCompleted") 4 { 5 // Iniciar, cargar o ejecutar métodos una 6 // vez obtenido un proyecto y toda su información 7 } 8 else if (e.PropertyName == "IsFaulted") 9 { 10 // Tratar casos de error 11 } 12 };

Referencias:

Espero que estos cuantos Tips hayan sido de utilidad.

Saludos desde lo que ha sido un gran puente del Corpus: Cervezas, buen pescadito, descanso y playa. Risa

Juanlu,ElGuerre

Comenzando con Xamarin y Visual Studio 2015

Muy buenas,

Hace ya un tiempo que no me dejo ver por aquí, el motivo no es otro que el de estar altamente centrado en otros menesteres. Espero sin embargo estar de nuevo al pie el cañón y dejarme ver, al menos, un más.

Tengo algunos posts sobre WPF, Windows Phone 8.1 y WinRT e incluso Windows 10 en el tintero, no me olvido de ellos, pero por el momento, dedicaré este post a mis primeros pasos con Xamarin.

Antes de Comenzar debemos saber que:

Existen algunos aspectos importantes en cuanto a las licencias:

Starter, tenemos algunas limitaciones, principalmente en cuanto al tamaño de cada ensamblado, que no puede superar los 64kb / 128kb. Aunque parece suficiente para probar,  he de decir que todo lo contrario, una solución creada directamente desde una plantilla de Visual Studio (“Native Shared”) ya supera este límite. En este caso, al compilar, obtendremos errores como los siguientes:

“User code size, 2945919 bytes, is larger than 131072 and requires a Business (or higher) License. XamarinApp1.Droid”

– “Using type `Android.Runtime.JNIEnv` requires Business (or higher) License.    XamarinApp1.Droid”

En estos enlaces: http://xamarin.com/faq#q18 y http://forums.xamarin.com/discussion/2912/xamarin-starter-edition-build-limits, podemos leer un poco más acerca de estas limitaciones.

Trial, tenemos 30 días para probar sin las restricciones anteriores. 30 días me parecen pocos, y  más cuando en esos días el tiempo dedicado a a las pruebas no es el deseado. Como solución, podemos crear una cuenta nueva cada 30 días !!! Pero…, antes de esto, nos aseguraremos de que la compañía en la que trabajamos no tienen acuerdos a este respecto. Es normal disponer de hasta 90 días e incluso renovaciones, y esto ya si comienza a ser un tiempo razonable para dar nuestros primeros pasos.

En cuanto al emulador Android de Xamarin, podemos echar un vistazo a este enlace: Introducing Visual Studio’s Emulator for Android.

Y por supuesto, no nos podemos olvidar de MVVM y Xamarin. Laurent Bugnion (GalaSoft), nos ayuda con esto: http://blog.galasoft.ch/posts/2014/10/my-xamarinevolve-talk-is-online-for-your-viewing-pleasure/

Conociendo estos aspectos entre otros, y, una vez instalado Visual Studio 2015 RC, lo primero es crear nuestro proyecto Xamarin, y para ello:

Elegimos la plantillas “Blank App (Native Shared)”:

image

Establecemos desde el Explorador de Soluciones, el proyecto “XamarinApp1.Droid” como proyecto de inicio (“Set as StartUp Project”).

Pulsamos “F5”, seleccionando previamente el emulador que con el que queramos trabajar.

image

En este punto nuestra aplicación se habría ejecutado sin más preámbulos en el emulador de Android, sin embargo, y como todo no puede ser fácil a la primera, nos encontramos con que el emulador no se inicia produciendo el mensaje: “Starting emulator: VS Emulator 5" KitKat (4.4) XXHDPI Phone”. Después de esperar durante un buen rato, el estado sigue siendo el mismo.

Tras varios intentos y el mismo resultado, decido probar los siguientes pasos:

-Borrar la carpeta correspondiente al emulador que no se inicia, en este caso: “C:Usersjuan.luis.guerreroAppDataLocalMicrosoftVisualStudioEmulatorAndroidContainersLocalDevicesvhd5_KitKat_(4.4)_XXHDPI_Phone”. Como hemos podido ver en el enlace, Introducing Visual Studio’s Emulator for Android, los emuladores de Android, son imágenes “.vhd” que funciona bajo Hyper-V y que se encuentra localizadas en estas carpetas.

– Desinstalar e instalar imágenes desde “Visual Studio Emulator for Android”

step 3

– Crear una nueva máquina virtual a partir de las imágenes .vhd anteriores de manera manual con “Hyper-V Manager”.

– Desinstalar “Microsoft Visual Studio Emulator for Android”

– Reparar la instalación de ,  “Microsoft Visual Studio 2015 RC”.

– Tras cada una de estas acciones, el problema continua, por lo que opto por desinstalar “Microsoft Visual Studio 2015 RC” y volver a instalarlo.

– Finalmente y tras un poco de paciencia, el emulador se inicia correctamente.

Cuando finalmente todo parece que funcionar, al iniciarse la aplicación, …. “Unfortunately, App1.Dro1 has stopped”.

screen1

Nos queda un paso más, desmarcar la casilla “Use Share Runtime”. Ahora sí,  la aplicación Xamarin se inicia por primera vez en el emulador de Android.

image step ok

Si alguien ha encontrado una mejor solución y quiere compartirla, Bienvenida será, así que, gracias de antemano por ello.

He de decir, que con Visual Studio 2015, los emuladores de Android han mejorado mucho. ¡Una recomendación, sin duda, para comenzar a crear aplicaciones Cross Platform con Xamarin!.

Por el momento, es todo, espero que este pequeño post aclare algunos pasos y evite alguna desesperación a algún principiante como yo.  Seguiremos profundizando en algunos de los aspectos comentados e iremos viendo otros.

Enjoy your cross Apps.
Juanlu, ElGuerre

Windows Phone 8.1. Primeras Best Practices con MVVM y GalaSoft.MvvmLight

image_thumb12Muy buenas,

En breve recibiremos la actualización oficial de este nuevo SO. No obstante, hace ya una semana que Windows Phone 8.1 está entre nosotros gracias a la aplicación “Preview for Developers”. Nuevas características y opciones nos tienen entretenidos. Noticias cada día sobre cómo configurarlo o cómo utilizar cada una de sus nuevas opciones. Josue Yeray y otros compañeros ya han posteado a este respecto como podemos ver aquí.

En cuanto al desarrollo, también nos encontramos con novedades:

  • Nuevos Emuladores y otras herramientas. Principalmente “Windows Phone Power Tools.”
  • Tareas de background y triggers: Notificaciones push, geofencing, etc.
  • Convergencia de APIs con Windows Store (aproximadamente un 90%).
  • Aplicaciones Universales para C# y WinJS (HTML 5 y JS).
  • Nuevas APIs para para la edición de vídeo y audio.
  • Etc.

Para no escribir más sobre estos temas, podemos echar un vistazo a este enlace.

Aunque existen compatibilidad total con proyectos Windows Phone 8.0, así como la posibilidad de migrar los proyectos de Windows Phone 8.0 a 8.1 (Silverlight), lo recomendable es comenzar a desarrollar proyectos WinRT basados en los nuevos modelos de aplicación para Windows Phone 8.1 y Windows. Modelos que podemos ver en la siguiente imagen:

image_thumb2

En cuanto al patrón MVVM, como siempre, Microsoft presenta su manera de implementarlo. Sin embargo, si al igual que yo, usas “GalaSoft.MvvmLight”, entonces tendrás que seguir algunas pautas, teniendo en cuenta que por el momento, no tenemos las plantillas que nos automatizan el trabajo.

Los siguientes pasos son los siguientes:

  • Crear un nuevo Proyecto Universal o Windows Phone 8.1 de tipo Hub App desde Visual Studio 2013 Update 2.
  • Añadir las referencias o instalar desde nuget el paquete MVVM Light Libraries only (PCL) 4.3.31.2.
  • Añadir la referencia a la DLL “Behaviors SDK (XAML)”. Es importante incluir ésta para poder hacer uso de triggers (“<i:Interaction.Behaviors>”)  desde el XAML y vincular así cada View con su ViewModel.

image_thumb4

  • Dentro del proyecto Shared, crear la carpeta ViewModel y añadir en su interior las clases ViewModel tal y como ya estábamos acostumbrados. De ésta manera tendremos algo como lo siguiente:

image_thumb10

Nota: Los interfaces son opcionales y, dependerán de hasta que punto queramos testear nuestra aplicación. Sin embargo, la interfaz IViewModelBase si que es necesaria. De la misma manera, como puede verse, la carpeta Common sólo contiene lo que vamos a necesitar además de una nueva clase, PageBase. Ambas, Interfaz y clase se muestran a continuación:

PageBase.cs

1 public class PageBase : Page 2 { 3 private readonly NavigationHelper navigationHelper; 4 private IViewModelBase viewModel; 5 6 public PageBase() : base() 7 { 8 this.NavigationCacheMode = NavigationCacheMode.Required; 9 10 this.navigationHelper = new NavigationHelper(this); 11 this.navigationHelper.LoadState += this.NavigationHelper_LoadState; 12 this.navigationHelper.SaveState += this.NavigationHelper_SaveState; 13 } 14 15 16 /// <summary> 17 /// Gets the <see cref="NavigationHelper"/> associated with this <see cref="Page"/>. 18 /// </summary> 19 public NavigationHelper NavigationHelper 20 { 21 get { return this.navigationHelper; } 22 } 23 24 ///// <summary> 25 ///// Populates the page with content passed during navigation. Any saved state is also 26 ///// provided when recreating a page from a prior session. 27 ///// </summary> 28 ///// <param name="sender"> 29 ///// The source of the event; typically <see cref="NavigationHelper"/> 30 ///// </param> 31 ///// <param name="e">Event data that provides both the navigation parameter passed to 32 ///// <see cref="Frame.Navigate(Type, object)"/> when this page was initially requested and 33 ///// a dictionary of state preserved by this page during an earlier 34 ///// session. The state will be null the first time a page is visited.</param> 35 private async void NavigationHelper_LoadState(object sender, LoadStateEventArgs e) 36 { 37 this.viewModel.Load(); 38 39 //BackgroundAccessStatus status = BackgroundExecutionManager.GetAccessStatus(); 40 //if (status == BackgroundAccessStatus.Denied || status == BackgroundAccessStatus.Unspecified) 41 //{ 42 // await BackgroundExecutionManager.RequestAccessAsync(); 43 //} 44 } 45 46 ///// <summary> 47 ///// Preserves state associated with this page in case the application is suspended or the 48 ///// page is discarded from the navigation cache. Values must conform to the serialization 49 ///// requirements of <see cref="SuspensionManager.SessionState"/>. 50 ///// </summary> 51 ///// <param name="sender">The source of the event; typically <see cref="NavigationHelper"/></param> 52 ///// <param name="e">Event data that provides an empty dictionary to be populated with 53 ///// serializable state.</param> 54 private void NavigationHelper_SaveState(object sender, SaveStateEventArgs e) 55 { 56 // TODO: Save the unique state of the page here. 57 } 58 59 60 #region NavigationHelper registration 61 62 ///// <summary> 63 ///// The methods provided in this section are simply used to allow 64 ///// NavigationHelper to respond to the page's navigation methods. 65 ///// <para> 66 ///// Page specific logic should be placed in event handlers for the 67 ///// <see cref="NavigationHelper.LoadState"/> 68 ///// and <see cref="NavigationHelper.SaveState"/>. 69 ///// The navigation parameter is available in the LoadState method 70 ///// in addition to page state preserved during an earlier session. 71 ///// </para> 72 ///// </summary> 73 ///// <param name="e">Event data that describes how this page was reached.</param> 74 protected override void OnNavigatedTo(NavigationEventArgs e) 75 { 76 base.OnNavigatedTo(e); 77 78 this.viewModel = (IViewModelBase)this.DataContext; 79 80 this.navigationHelper.OnNavigatedTo(e); 81 } 82 83 protected override void OnNavigatedFrom(NavigationEventArgs e) 84 { 85 base.OnNavigatedFrom(e); 86 this.navigationHelper.OnNavigatedFrom(e); 87 } 88 89 #endregion 90 }

IViewModelBase.cs

1 public interface IViewModelBase 2 { 3 void Load(); 4 }

  • La clase PageBase permite la navegación entre páginas (NavigationHelper) que a su vez hace uso de la clase (SuspensionManager) y que van a permitir a nuestra aplicación:
    1. Mayor rapidez en la carga de cada página. Evitaremos incluir en el constructor de cada ViewModel la carga de datos. Ésta pasará a implementarse en el método “Load” indicado por el interface IViewModelBase. Por ello, cada ViewModel que creemos deberá implementar esta interfaz. Puede verse como la clase PageBase, en las líneas 37 y 78 se encargan de llevar a cabo todo esta labor.
    2. Mantener el estado de cada página para que en caso de que la aplicación quede suspendida pueda volver a ser activada en su estado anterior. Recordemos que ahora Windows Phone 8.1 no cierra las aplicaciones al volver hacia atrás (con el botón Back).
  • Modificar la página MainPage.xaml como se muestra a continuación para que pase a hacer uso de la clase PageBase:
1 <common:PageBase 2 x:Class="elGuerre.MainPage" 3 xmlns:common="using:elGuerre.Common" 4 ... 5 > 6 </common:PageBase>

  • Con todos estos cambios, la clase MainPage.xaml.cs quedará totalmente limpia. Simplemente heredando de PageBase.
1 public sealed partial class MainPage : PageBase 2 { 3 public MainPage() 4 { 5 this.InitializeComponent(); 6 } 7 }

 

  • Modificar la página MainPage.xaml nuevamente para hacer uso de los behaviors y vincular los eventos con los eventos RelayCommand de nuestro ViewModel:
1 ... 2 xmlns:i="using:Microsoft.Xaml.Interactivity" 3 xmlns:core="using:Microsoft.Xaml.Interactions.Core" 4 ... 5 <HubSection x:Uid="HubSection1" Header="SECTION 1" > 6 <DataTemplate> 7 <ListView 8 ItemsSource="{Binding Groups}" 9 IsItemClickEnabled="True" 10 ContinuumNavigationTransitionInfo.ExitElementContainer="True" 11 > 12 <ListView.ItemTemplate> 13 ... 14 </ListView.ItemTemplate> 15 16 <i:Interaction.Behaviors> 17 <i:BehaviorCollection> 18 <core:EventTriggerBehavior EventName="ItemClick"> 19 <core:InvokeCommandAction Command="{Binding ClickSectionCommand}" /> 20 <!--<core:CallMethodAction MethodName="Method1" TargetObject="{Binding Mode=OneWay}" />--> 21 </core:EventTriggerBehavior> 22 </i:BehaviorCollection> 23 </i:Interaction.Behaviors> 24 25 </ListView> 26 </DataTemplate> 27 </HubSection> 28 29 ... 30 31 <HubSection x:Uid="HubSection2" Header="SECTION 2" Width="Auto" > 32 <DataTemplate> 33 <GridView 34 ItemsSource="{Binding Groups[0].Items}" 35 AutomationProperties.AutomationId="ItemGridView" 36 AutomationProperties.Name="Items In Group" 37 ItemTemplate="{StaticResource Standard200x180TileItemTemplate}" 38 SelectionMode="None" 39 IsItemClickEnabled="True" 40 ContinuumNavigationTransitionInfo.ExitElementContainer="True"> 41 <GridView.ItemsPanel> 42 <ItemsPanelTemplate> 43 <ItemsWrapGrid /> 44 </ItemsPanelTemplate> 45 </GridView.ItemsPanel> 46 47 <i:Interaction.Behaviors> 48 <i:BehaviorCollection> 49 <core:EventTriggerBehavior EventName="ItemClick"> 50 <core:InvokeCommandAction Command="{Binding ClickItemCommand}" /> 51 </core:EventTriggerBehavior> 52 </i:BehaviorCollection> 53 </i:Interaction.Behaviors> 54 55 </GridView> 56 </DataTemplate> 57 </HubSection>

  • Nota: Se ha eliminado de los controles HubSection el DataContex con objeto de que los behabiors encuentren los RelayCommand en su ViewModel. Por lo que el DataContext es el siguiente teniendo como Source al contenedor de dependencias (SimpleIoC). De igual forma los ItemSource han sido modificados por el mismo motivo.
DataContext="{Binding Main, Mode=OneWay, Source={StaticResource Locator}}"

 

  • Como punto final. La implementación de MainViewModel es la siguiente:
1 public class MainViewModel : ViewModelBase, IMainViewModel 2 { 3 public const string TitlePropertyName = "Title"; 4 5 private string title = string.Empty; 6 private readonly IDataService dataService; 7 private NavigationHelper navHelper; 8 private ObservableCollection<SampleDataGroup> groups; 9 10 /// <summary> 11 /// Initializes a new instance of the MainViewModel class. 12 /// </summary> 13 public MainViewModel(IDataService dataService) 14 { 15 16 this.dataService = dataService; 17 18 // Tiempo de diseño 19 if (this.IsInDesignMode) 20 this.Load(); 21 } 22 23 public async void Load() 24 { 25 var data = await this.dataService.GetData(); 26 this.Groups = new ObservableCollection<SampleDataGroup>(data); 27 } 28 29 public ObservableCollection<SampleDataGroup> Groups 30 { 31 get { return this.groups; } 32 set { Set(() => Groups, ref this.groups, value); } 33 } 34 35 public string Title 36 { 37 get{ return title; } 38 set { Set(() => Title, ref this.title, value); } 39 } 40 41 public void Method1() 42 { 43 44 } 45 46 private RelayCommand<ItemClickEventArgs> clickSectionCommand; 47 public RelayCommand<ItemClickEventArgs> ClickSectionCommand 48 { 49 get 50 { 51 return clickSectionCommand 52 ?? (clickSectionCommand = new RelayCommand<ItemClickEventArgs>( 53 (e) => 54 { 55 var groupId = ((SampleDataGroup)e.ClickedItem).UniqueId; 56 this.navHelper = new NavigationHelper(); 57 this.navHelper.NavigateTo<SectionPage>(groupId); 58 })); 59 } 60 } 61 62 63 private RelayCommand<ItemClickEventArgs> clickItemCommand; 64 public RelayCommand<ItemClickEventArgs> ClickItemCommand 65 { 66 get 67 { 68 return clickItemCommand 69 ?? (clickItemCommand = new RelayCommand<ItemClickEventArgs>( 70 (e) => 71 { 72 var itemId = ((SampleDataItem)e.ClickedItem).UniqueId; 73 this.navHelper = new NavigationHelper(); 74 this.navHelper.NavigateTo<ItemPage>(itemId); 75 })); 76 } 77 } 78 }

  • Modificar las páginas SectionPage e ItemPage siguiendo estos mismo pasos creando igualmente los ViewModel correspondientes.
  • Si hemos optado por una aplicación Universal, sería necesario repetir los pasos únicamente para las páginas .XAML.

Tras estos pasos y algunos comentarios que encontraremos al analizar el código, no nos será nada complicado tener nuestro primer conjunto de buenas prácticas para la implementación de aplicaciones Universales y/o aplicaciones Windows Phone 8.1.  ¡Espero haber conseguido ese objetivo!

Saludos @Home

@JuanluElGuerre