jueves, 2 de julio de 2015
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 SDKs\Windows Phone\v8.1\Tools”) y se basa en línea de comandos. Su uso es el siguiente:
1. Listado de emuladores instalados. “ISETool.exe 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).
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:
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:
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”.
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.
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.
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”.
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!
Espero que sea de utilidad.
Saludos
Juanlu
Etiquetas: Visual Studio 2015, Windows 10, Windows Phone, Windows Phone 8.1, WinRT
domingo, 7 de junio de 2015
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="" 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:
- C# async and await: Why Do We Need Them?: http://blogs.msdn.com/b/cdndevs/archive/2013/12/18/c-async-and-await-why-do-we-need-them-part-1.aspx
- AsycnLazy<T> http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx
- NotifyTaskCompletion: http://msdn.microsoft.com/en-us/magazine/dn605875.aspx
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.
Juanlu,ElGuerre
Etiquetas: Windows 8, Windows Phone, Windows Phone 8.1, WinRT