Hace un tiempo comencé a implementar un cliente “estilo Metro” para el servicio web Snipt, un servicio que permite guardar, compartir o descubrir “snippets” o trozos útiles y altamente reutilizables de código. La UI del cliente es muy simple y está casi completamente hecho a partir de las plantillas de Visual Studio 2012 para aplicaciones estilo Metro, en concreto con la llamada Split Page.
Los elementos del feed público del servicio se van cargando en la página principal (la primera que ve el usuario) y según la que esté seleccionada se muestra el código en un recuadro en la parte derecha. Hasta aquí todo bien, la chicha llega cuando queremos cargar los elementos desde la web a nuestra aplicación. ¿Cuántas cargamos a la vez? ¿Las cargamos al inicio de la aplicación o en la construcción de la página? Podríamos cargar las 4 ó 5 primeras páginas del feed público, pero nos llevaría muchísimo tiempo y eso haría esperar al usuario, incumpliendo el principio de ser rápido y fluido del lenguaje de diseño Metro. Podríamos cargar unos pocos rápidamente pero si no cargamos más al usuario casi le compensaría más visitar la web del servicio debido a la escasez de resultados.
Así pues lo ideal es cargar cuanto más ítems mejor, pero mostrando una partida inicial al usuario para que pueda empezar a interactuar de inmediato con nuestra aplicación mientras los demás ítems se cargan en un segundo plano y se añaden a nuestra lista cuando ya están descargadas. Este resultado se puede alcanzar de dos maneras, creando una clase personalizada que implemente la interfaz ISupportIncrementalLoading que permitirá cargar más ítems cuando el usuario esté llegando al final de la lista que le presentamos inicialmente; o bien cargando unos pocos ítems para no ralentizar la aplicación, y seguir cargando más ítems en segundo plano. La primera opción es la más idónea en cuanto que se ajusta mejor al propio diseño Metro y tiene un comportamiento más natural y además cargará automáticamente más ítems según el usuario lo requiera, pero si te pasa como a mi, que no he podido implementar dicha interfaz sin romperme la cabeza, siempre puedes tirar por la segunda solución para salir del paso de una manera medianamente elegante.
El plan
Lo que haremos será cargar una cantidad pequeña de ítems al inicio de nuestra aplicación aprovechando el tiempo que nos da el Splash Screen de nuestra aplicación, digamos que cargamos 10 ítems. Una vez cargados le presentamos al usuario nuestra página principal con esos 10 ítems cargados pero a la vez seguiremos cargando una cantidad mayor de ítems y cuando los tengamos listos los añadiremos a la lista que ve el usuario, así ofrecemos una cantidad de ítems interesante para el usuario manteniendo nuestra aplicación rápida y sin bloqueos.
La implementación
En mi caso tengo una clase que realiza todas las llamadas y peticiones al servicio web, llamada genéricamente ApiManager. Esta clase tiene un miembro de tipo ObservableCollection<Snippet> llamado Snippets que contiene como veis una clase personalizada llamada Snippets y que debéis reemplazar por el tipo de ítems que useis en vuestra aplicación.
ApiManager también cuenta con un método encargado de obtener los ítems del timeline público del servicio web y que tiene una firma como la siguiente:
public async Task GetPublicSnippets(ObservableCollection<Snippet> list, int? limit, int offset)
Lo primero que recibe es una lista de elementos que rellenará con lo que le llegue del servicio web, y en este caso en concreto se le puede poner un límite para cargar una cantidad determinada de ítems y un “desfase” para empezar a cargar a partir de cierto número de ítems.
Dentro de este método se realiza una petición web de tipo GET, se convierte la respuesta JSON a un objeto nativo .NET (en este caso una lista de elementos) y se van añadiendo elementos a la lista que se pasa como parámetro, llenando así la susodicha.
Lo que haremos será llamar a este método para que llene el miembro Snippets de ApiManager cuando se inicie el programa, así que buscamos el método OnLaunched en el archivo App.xaml.cs de nuestro proyecto y añadimos lo siguiente (el recurso “sniptApiManager” está declarado en App.xaml como un recurso de la aplicación)
SniptApiManager localManager = (SniptApiManager)App.Current.Resources["sniptApiManager"];
if (localManager != null)
if (localManager.Snippets.Count == 0)
await localManager.GetPublicSnippets(localManager.Snippets, 10, 0);
Con esto cargamos los primeros 10 ítems de manera asíncrona (si no estás muy puesto en el funcionamiento de los nuevos métodos asíncronos de .NET 4.5 mira aquí), ahora solo falta mostrárselos al usuario. Busca el método LoadState en el archivo MainPage.xaml.cs (o como hayas llamado a tu página principal) y añade lo siguiente al comienzo del mismo
SniptApiManager localManager = (SniptApiManager)App.Current.Resources["sniptApiManager"];
this.DefaultViewModel["Group"] = localManager;
this.DefaultViewModel["Items"] = localManager.Snippets;
Aquí agrego el objecto “contenedor” de elementos como el grupo del modelo de datos, y el miembro Snippets como el conjunto de ítems a mostrar. Aquí podremos usar tanto ObservableCollection como List<T> u otro objeto de lista. Agregando ambos elementos al modelo de datos la UI de nuestra aplicación se actualizará ella solita y el usuario podrá interactuar con estos datos. Pero aún no hemos terminado, ahora vamos a cargar otros 20 ítems más; justo después de estas líneas añadimos lo siguiente
if (localManager.Snippets.Count < 30)
{
await localManager.GetPublicSnippets(localManager.Snippets, null, localManager.Snippets.Count);
}
La condición está puesta para que nuestra página no añada más elementos una vez alcanzado los 30 (sin un límite fijo el miembro Snippet de ApiManager crecería sin fin y acabaría ocupando una cantidad de memoria exagerada), y para que no cargue los ítems que ya tenemos usamos el parámetro offset para que ApiManager descargue a partir del último ítems que conseguimos del servicio web.
Fíjate en que no hace falta añadir más, pues al actualizar el objeto asignado al modelo de datos, este mismo se actualiza automáticamente sin que tengamos que preocuparnos de reasignar el objeto al modelo de datos. De esta forma hemos cargado una buena cantidad de ítems sin congelar nuestra aplicación ni hacer esperar demasiado al usuario.

Página inicial de Snipts
Cosas por mejorar
Pues evidentemente, hacer una implementación de ISupportIncrementalLoading que sería lo ideal en este tipo de escenarios, pero al margen de eso hay ciertas cuestiones que pueden mejorar la experiencia del usuario por ejemplo, agregar una barra de progreso indeterminado en la parte superior de la página, indicando al usuario de que, aunque puede interactuar sin problemas con la aplicación, aún se está realizando algún trabajo en segundo plano (cargar más ítems en nuestro caso).
También se podría guardar la posición del elemento seleccionado antes de añadir los nuevos ítems, para así no volver al primer elemento una vez cargados y desconcertar al usuario.