Windows Phone 8 Tip: Fast App Resume

Hola a todos!

Hoy vengo con un pequeño truco, que mejorará increíblemente la experiencia de uso de nuestra aplicación. Se trata de una nueva característica de Windows Phone 8 llamada Fast App Resume.

Pero… ¿Qué es esto exactamente? Bueno, desde Windows Phone 7.5, sabemos que nuestra aplicación queda suspendida cuando el usuario va a la pantalla inicial. Si presionamos el botón atrás durante unos segundos, aparece el gestor de aplicaciones, donde vemos todas las aplicaciones abiertas (y suspendidas) en ese momento, podemos pulsar sobre una y automáticamente se vuelve a activar. Esto es lo que conocemos como FAS, Fast Application Switching. Pero si, en vez de usar este método, el usuario vuelve a pulsar sobre el icono de la aplicación o el Tile, la instancia suspendida es eliminada de memoria y se crea una nueva instancia. Esto, por supuesto, es mucho más lento que recuperar una instancia ya creada. Para solventar este inconveniente, Windows Phone 8 soporta además de FAS, un nuevo modo llamado FAR, Fast Application Resume. FAR nos permite indicar al sistema que deseamos mantener la instancia actualmente creada aunque el usuario pulse sobre el Tile principal de la aplicación. De esta forma solo se tiene que activar la instancia y no destruirla y crearla de nuevo. Por supuesto esto es muchísimo más rápido. Si estás mirando que características añadir a una aplicación migrada desde Windows Phone 7.5 a 8.0, esta es indispensable.

FAR: Fast Application Resume

¿Y como lo activamos? Aquí viene la mejor parte, solo tenemos que modificar una línea del XML del manifiesto de nuestra aplicación para activarlo. Tenemos que buscar el elemento DefaultTask y añadirle el atributo ActivationPolicy:

<Tasks>
  <DefaultTask  Name ="_default" NavigationPage="MainPage.xaml" ActivationPolicy="Resume"/>
</Tasks>

Este atributo ActivationPolicy puede recibir dos valores: Resume o Replace. Resume activa el Fast Application Resume, indicando que siempre que sea posible la aplicación debe recuperar la instancia activa. Replace es el valor por defecto si no indicamos Resume y genera el comportamiento normal, la aplicación inicia una nueva instancia.

¿Como podemos comprobar la diferencia entre ambos modos? Podemos verlo de forma muy sencilla que nos ilustrará los efectos del FAR. Vamos a crear una aplicación Windows Phone 8 que tenga un TextBox en su página principal. Sin indicar el valor del ActivationPolicy, hagamos los siguiente pasos:

  1. Desplegamos en el dispositivo o emulador.
  2. Escribimos “Hola app” en el TextBox.
  3. Presionamos el botón inicio.
  4. Vamos a la lista de aplicaciones y pinchamos de nuevo sobre nuestra aplicación.

¿Cuál es el resultado? El texto “Hola app” ha desaparecido, bueno, en realidad nunca estuvo escrito, puesto que al pinchar sobre la aplicación, se ha destruido la instancia suspendida (donde estaba el texto) y se ha creado una nueva. Vamos a repetir la prueba, solo que ahora, modificaremos nuestro manifiesto para añadir el atributo ActivationPolicy=”Resume”. ¿Cual es el resultado? Efectivamente, el texto permanece en su sitio, puesto que lo que hemos realizado es recuperar la copia en memoria.

Cambios en la navegación

Aunque, como hemos visto, implementar FAR es muy sencillo y sus ventajas son obvias, queda un asunto por discutir: la navegación. ¿Qué experiencia de navegación deseamos ofrecer al usuario? Dependiendo de como activemos la aplicación, cambiará la experiencia de navegación que obtendremos:

  • Si activamos la aplicación desde un tile o desde la lista de aplicaciones: Se reiniciará la pila de navegación y a continuación se realizará una navegación a la página que corresponda.
  • Si volvemos al la aplicación con el botón atrás o desde la lista de apps abiertas: Volveremos a la página abierta, conservando la pila de navegación.

Esta diferencia se da debido a que, al navegar desde un tile o la lista de aplicaciones, recibimos dos navegaciones. La primera de ellas va dirigida a la página principal de nuestra aplicación con el modo de navegación establecido en Reset. Este modo es manejado por el método CheckForResetNavigation de la clase App (App.xaml.cs) y se encarga de limpiar la pila de navegación:

private void CheckForResetNavigation(object sender, NavigationEventArgs e)
{
    // If the app has received a 'reset' navigation, then we need to check
    // on the next navigation to see if the page stack should be reset
    if (e.NavigationMode == NavigationMode.Reset)
        RootFrame.Navigated += ClearBackStackAfterReset;
}

private void ClearBackStackAfterReset(object sender, NavigationEventArgs e)
{
    // Unregister the event so it doesn't get called again
    RootFrame.Navigated -= ClearBackStackAfterReset;

    // Only clear the stack for 'new' (forward) and 'refresh' navigations
    if (e.NavigationMode != NavigationMode.New && e.NavigationMode != NavigationMode.Refresh)
        return;

    // For UI consistency, clear the entire page stack
    while (RootFrame.RemoveBackEntry() != null)
    {
        ; // do nothing
    }
}

A continuación recibimos una segunda navegación, esta vez a la página que deseamos llegar y con el modo establecido en New. De esta forma, aunque hemos recuperado la instancia activa de la aplicación, que es mucho más rápido que abrir una nueva, tenemos la impresión de estar ante una nueva instancia. Pero puede ocurrir que deseemos ofrecer una experiencia diferente al usuario, haciendo que al pulsar un tile principal o la lista de aplicaciones, vuelva a la página activa antes de suspender la aplicación. Para esto tendremos que controlar la forma en la que se está iniciando la aplicación. Sabemos que en el evento CheckForResetNavigation un modo de Reset significa que se inicia desde un tile o lista de aplicación y que a continuación navegará a la página indicada, poniéndola encima del resto en la pila de navegación. podríamos modificar el CheckForResetNavigation para hacer algo parecido a esto:

private bool loadingfromInstance = false;

private void CheckForResetNavigation(object sender, NavigationEventArgs e)
{
    if (e.NavigationMode == NavigationMode.New && this.loadingfromInstance)
        RootFrame.GoBack();

    // If the app has received a 'reset' navigation, then we need to check
    // on the next navigation to see if the page stack should be reset
    if (e.NavigationMode == NavigationMode.Reset)
        this.loadingfromInstance = true;
}

De esta forma, en la primera navegación con el modo Reset, no eliminamos la pila de navegación y marcamos a true la variable loadingFromInstance. En la segunda navegación, con el modo New, controlamos que previamente hayamos recibido un Reset. Si es así, navegamos atrás, para volver a la página en la que dejamos la aplicación. De cara al usuario, el resultado es recuperar la aplicación en el mismo punto exacto en que la dejó y con la misma pila de navegación. Un ejemplo de este comportamiento lo tenemos en la aplicación de Facebook para Windows Phone 8, que se comporta exactamente de esta manera.

Pero con este código, veremos que tenemos un inconveniente. Primero se carga la página principal y a continuación se navega hacia atrás y se muestra la página en la que nos encontrábamos, haciendo un efecto bastante feo. Para arreglar esto, debemos modificar el método InitializePhoneApplication para añadir un manejador al evento Navigating del objeto RootFrame. En este manejador, comprobaremos si estamos navegando a la página principal y si loadingFromInstance es true. Si se cumplen ambas condiciones, cancelaremos la navegación.

Es importante que volvamos a establecer loadingFromInstance a false una vez que realicemos este paso o cualquier tipo de navegación a la página principal será cancelada:

void RootFrame_Navigating(object sender, NavigatingCancelEventArgs e)
{
    if (e.Uri.ToString().Contains("MainPage.xaml") && this.loadingfromInstance)
    {
        e.Cancel = true;
        this.loadingfromInstance = false;
    }
}

De esta forma, no necesitamos hacer el GoBack en nuestro método CheckForResetNavigation, simplemente no navegamos a la página principal si estamos recuperando la instancia, quedando el código de ese método mucho más sencillo como podemos ver a continuación:

private bool loadingfromInstance = false;

private void CheckForResetNavigation(object sender, NavigationEventArgs e)
{
    if (e.NavigationMode == NavigationMode.Reset)
        this.loadingfromInstance = true;
}

Y Voila! Ya tenemos nuestra aplicación recuperando la instancia anterior y dejando al usuario en el mismo sitio exacto en el que estaba.

Conclusión

El FAR o Fast Application Resume en Windows Phone 8 es una de las novedades más útiles y a la vez más pasada por alto que nos ofrece la nueva versión del sistema operativo. Podemos ofrecer al usuario una experiencia de recuperación de estado mucho mejor y una velocidad de inicio de nuestra aplicación mayor, usando este simple truco. En nuestras manos queda como manejar la pila de navegación, si queremos ofrecer una experiencia de continuidad o simplemente aprovecharnos de la rapidez de inicio y reiniciar la navegación para que se asemeje a un arranque desde cero.

Como siempre, a continuación os dejo un pequeño ejemplo del uso de FAR, con una aplicación que contiene dos páginas y nos permite crear un tile secundario para probar la navegación desde diferentes puntos.

Un saludo y Happy Coding!

Grupo de desarrolladores hispanos de Windows Phone 8 en G+

Hola a todos!

image Oscar Gutiérrez de Nokia Spain ha creado un grupo de desarrolladores en Google plus para Windows Phone 8. Tendremos las últimas noticias por parte de Nokia, tutoriales, artículos e incluso algunas charlas en vivo sobre la plataforma. Creo que es un recurso increíble para mantenerte en contacto con la comunidad y quería compartirlo con todos!

Si te interesa el desarrollo para Windows Phone 8, no puedes dejar de pasar por aquí

[Evento] Windows Phone 8 & PhoneGap en Barcelona

Hola a todos!

Plain Conceptslogonokia

WinPhone8Logophonegaplogo

¿Qué ocurre cuando, en una misma sala unes a Plain Concepts, Nokia y el Google Developers Group de Barcelona? Pues que se lía… eso os lo aseguro!!

Y como nos gusta liarla, el 11 de Febrero, desde las 9:30 de la mañana hasta las 18:00 estaremos en Barcelona Alfredo Fernández, Gerard López y un servidor por parte de Plain Concepts y Oscar Gutiérrez de Nokia Spain, para hacer un evento sobre Windows Phone 8 y las novedades que nos ofrece y lo fácil que es desarrollar para la plataforma. Pero queríamos ir un paso más allá y ver las posibilidades que nos brindan HTML5 y Javascript para desarrollar aplicaciones multiplataforma. ¿Quién mejor que Alfredo Fernandez y Gerard Lopez para enseñarnos las bondades de PhoneGap? Entre los dos, ya han publicado apps profesionales para Android, iOS y BlackBerry desarrolladas totalmente en PhoneGap, aplicaciones con acceso a servicios externos, uso de propiedades del sistema, multi resolución, con uso de Media queries y demás trucos modernos de CSS3.

Además, gracias a la colaboración de Oscar Gutiérrez de Nokia Spain tendremos Algunos dispositivos con Windows Phone 8 para que podáis probar código en ellos y ver como funcionan y algunas sorpresas más…

Si os apetece pasar un día divertido, desarrollando y conociendo los últimos detalles sobre XAML + C#, HTML5/JS y el desarrollo nativo/multiplataforma en Windows Phone 8 y PhoneGap, no podéis dejar de venir. 

Aquí tenéis el registro del evento que, por supuesto y de la mano de Plain Concepts, es totalmente gratuito!!

Espero veros a todos, pasaremos lista!

Un saludo y Happy Coding!

Windows Phone 8: Asociación de archivos y protocolos

Hola a todos!

Una de las novedades de Windows Phone 8 es la capacidad de asociar nuestra aplicación a tipos de archivos. De esta forma, cuando se intente acceder a un tipo de archivo para el cual nos hayamos registrado, se abrirá nuestra aplicación, si es la única que existe capaz de manejarlos o se mostrará una lista de aplicaciones disponibles.

Quizás en un primer momento pueda parecer algo que solo puede ser útil en ciertas condiciones muy específicas. Nada más lejos de la realidad, como vamos a ver a continuación.

Asociación de archivos

Al crear una asociación entre nuestra aplicación y un tipo de archivo, facilitamos que se lance automáticamente cuando el usuario quiera abrir un archivo en particular. El lanzamiento de nuestra aplicación puede producirse directamente desde el navegador, desde un correo electrónico, desde otra aplicación o incluso desde un archivo recibido por NFC o Bluetooth.

La asociación también determina el tipo de archivos que podemos leer desde el almacenamiento de la tarjeta SD extraíble, pero eso lo veremos en otro post.

Lo primero que necesitaremos para crear la asociación en nuestra aplicación a un tipo de archivo en concreto, son los logotipos a usar para mostrar el archivo en cuestión. En concreto necesitaremos tres imágenes: Una de 33×33 píxeles para mostrar si el archivo llega como adjunto en un correo electrónico. La segunda de 69×69 píxeles para mostrar en el hub de office. Por último necesitaremos otra de 176×176 píxeles para mostrar en la descarga del navegador.

image

Una vez que tengamos nuestros archivos de logotipo en los tres tamaños, podemos crear la asociación. Para ello tenemos que editar el archivo WMAppManifest.xml de nuestra aplicación, con el editor XML (Botón derecho Open With > XML (Text) Editor). Tendremos que añadir una sección llamada Extensions, justo debajo de Tokens. Dentro de esta sección crearemos un nodo FileTypeAssociation donde indicaremos el nombre, la TaskID asociada y el fragmento de Uri que se enviará a nuestra aplicación:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="fileToken=%s">
  </FileTypeAssociation>
</Extensions>

La propiedad Name se usará para mostrar el nombre del tipo de archivo. TaskID se refiere al TaskID de la sección Token, que define el arranque de nuestra aplicación, y que por defecto es _default. NavUriFragment indica el parámetro que se enviará a nuestra aplicación con el token del archivo, para poder abrirlo.

Una vez definidos estos tres valores, dentro de FileTypeAssociation incluiremos una sección Logos, donde indicaremos los tres logos que ya hemos incluido en nuestra aplicación:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="fileToken=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/filetype_small.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/filetype_medium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/filetype_large.png</Logo>
    </Logos>
  </FileTypeAssociation>
</Extensions>

En cada logo indicamos su tamaño en la propiedad Size y en el contenido ponemos la ruta relativa, marcando a true la propiedad IsRelative. Debemos establecer los archivos como Content en su Build Action y Copy Always o Copy if newer en su propiedad Copy to Output Directory. A continuación solo nos queda indicar exactamente las extensiones de archivo que deseamos asociar, añadiendo dentro del nodo FileTypeAssociation la sección SupportedFileTypes:

<Extensions>
  <FileTypeAssociation Name="geeksms file" TaskID="_default" NavUriFragment="file=%s">
    <Logos>
      <Logo Size="small" IsRelative="true">Assets/filetype_small.png</Logo>
      <Logo Size="medium" IsRelative="true">Assets/filetype_medium.png</Logo>
      <Logo Size="large" IsRelative="true">Assets/filetype_large.png</Logo>
    </Logos>
    <SupportedFileTypes>
      <FileType ContentType="application/text">.geeksms</FileType>
    </SupportedFileTypes>
  </FileTypeAssociation>
</Extensions>

Dentro de la sección SupportedFileTypes incluimos un nodo FileType por cada extensión de archivo que deseemos asociar al tipo geeksms file. En este caso añadimos solo una, indicamos el tipo de contenido en su propiedad ContentType y la extensión, con el punto. Con esto hemos concluido la configuración de nuestro archivo asociado. Ahora tenemos que saber, de alguna forma, que nos están activando con un archivo asociado. Para ello usaremos una clase que herede de UriMapperBase. Siempre que nuestra aplicación se active desde un archivo asociado, recibiremos un enlace que contendrá el fragmento: “/FileTypeAssociation?” seguido del fragmento que hayamos indicado y el token del archivo. Lo que vamos a hacer es crear una clase nueva en nuestra aplicación, llamada FileUriMapper, que procese la Uri de lanzamiento de nuestra aplicación en busca del archivo. Si encuentra una asociación nos dirigirá a una página, de lo contrario iremos a otra:

public class FileUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        string tempUri = uri.ToString();

        if (tempUri.Contains("/FileTypeAssociation?"))
        {
            int fileIDIndex = tempUri.IndexOf("fileToken=") + 10;
            string fileID = tempUri.Substring(fileIDIndex);

            string fileUri = string.Format(@"/SecondPage.xaml?file={0}", fileID);

            return new Uri(fileUri, UriKind.Relative);
        }
        else
            return uri;
    }
}

En esta clase, simplemente examinamos la Uri que nos llega. Si esta no contiene el fragmento “/FileTypeAssociation?”, devolvemos la misma Uri, pues se trata de un lanzamiento normal. Por el contrario, si lo contiene, buscamos el token, fileID, del archivo y se lo pasamos como parámetro a una página llamada SecondPage que se encargará de tratarlo. Ahora tenemos que modificar el método InitializePhoneApplication de la clase App para añadir nuestro FileUriMapper al Frame de la aplicación:

private void InitializePhoneApplication()
{
    if (phoneApplicationInitialized)
        return;

    RootFrame = new PhoneApplicationFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;
            
    RootFrame.UriMapper = new FileUriMapper();

    RootFrame.NavigationFailed += RootFrame_NavigationFailed;
    RootFrame.Navigated += CheckForResetNavigation;

    phoneApplicationInitialized = true;
}

Simplemente establecemos la propiedad UriMapper de RootFrame a una nueva instancia de nuestro mapeador. Así cuando se inicie la aplicación, la Uri de inicio pasará por nuestra clase, que podrá examinarla y devolver la Uri correcta en cada caso. En el método OnNavigatedTo de la página SecondPage.xaml, vamos a examinar la QueryString en busca del token de archivo.:

protected override async void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    string fileToken = NavigationContext.QueryString["file"];

    var file = await SharedStorageAccessManager.CopySharedFileAsync(ApplicationData.Current.LocalFolder, "File.geeksms", NameCollisionOption.ReplaceExisting, fileToken);
    var stream = await file.OpenReadAsync();

    IBuffer buffer = new Windows.Storage.Streams.Buffer((uint)stream.Size);
    await stream.ReadAsync(buffer, (uint)stream.Size, InputStreamOptions.None);
    DataReader reader = DataReader.FromBuffer(buffer);
    textContent.Text = reader.ReadString(buffer.Length);
}

Con este token, podemos usar el método CopySharedFileAsync de la clase SharedStorageAccessManager. Este método nos devuelve una instancia de IStorageFile, con la que podremos acceder al stream del archivo, ya copiado en el espacio de nuestra aplicación. Con esto solo nos queda leer su contenido usando el método ReadAsync y un DataReader con el método ReadString para finalmente obtener el contenido en formato de texto.

image

Asociación de protocolos

De una forma muy parecida a como hemos realizado la asociación de archivos, podemos llevar a cabo una asociación de protocolos. ¿A qué nos referimos exactamente con un protocolo? Por regla general, nos referimos a una URL, que podría ser algo como lo siguiente:

geeksms:showarticle?ArticleId=100

Podemos asociar nuestra aplicación al protocolo geeksms: y recibir el resto de parámetros para decidir en que página iniciar la aplicación o que datos cargar. El primer paso para llevar a cabo esto, es registrar el protocolo al que deseamos responder (o protocolos, pueden ser más de uno). Lo realizaremos en el archivo WMAppManifest.xml, al igual que el registro de tipo de archivos:

<Extensions>
  <Protocol Name="geeksms" TaskID="_default" NavUriFragment="encodedLaunchUri=%s"/>
</Extensions>

Creamos una sección Extensions, si no existe ya, y dentro añadimos un nodo Protocol. El nombre debe ser único. Los otros dos parámetros son fijos, la tarea asociada (_default es la que abre nuestra aplicación) y el fragmento de navegación. Este fragmento debe ser siempre el mostrado en este ejemplo, dentro llegará la URL enviada a nuestra aplicación. Para poder saber cuando se está activando la aplicación desde un protocolo, usaremos una clase UriMapper, igual que con la asociación de archivos:

public class ProtocolUriMapper : UriMapperBase
{
    public override Uri MapUri(Uri uri)
    {
        string tempUri = HttpUtility.UrlDecode(uri.ToString());

        if (tempUri.Contains("geeksms:ShowArticle?ArticleId="))
        {
            string navUri = string.Format(@"/Views/SecondPage.xaml?protocol={0}", HttpUtility.UrlEncode(tempUri));
            return new Uri(navUri, UriKind.Relative);
        }
        return uri;
    }
}

En el método OnNavigatedTo de nuestra página SecondPage, obtendremos el parámetro protocol que enviamos para mostrarlo en pantalla:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo(e);

    protocolTextBlock.Text = NavigationContext.QueryString["protocol"];
}

¿Como podemos probar que está funcionando? La activación por protocolo solo puede realizarse desde otra aplicación. Para ello haremos uso de otra novedad de Windows Phone 8, la clase Launcher, del namespace Windows.System.

Bonus: LaunchUriAsync y LaunchFileAsync

Otra de las novedades de Windows Phone 8 es la clase Launcher, del namespace Windows.System. Esta clase contiene dos métodos: LaunchUriAsync y LaunchFileAsync. Estos nos permiten lanzar de una forma sencilla una URL o un archivo.

LaunchUriAsync nos permite lanzar una URL y el sistema se encargará de buscar una aplicación asociada. Por ejemplo, podemos usar una dirección de un sitio web, de la siguiente forma:

await Windows.System.Launcher.LaunchUriAsync(new Uri("http://www.geeks.ms"));

Que dará como resultado la apertura de Internet Explorer. Pero también podremos lanzar una URL con un protocolo propio, lo que ejecutará nuestra aplicación:

await Windows.System.Launcher.LaunchUriAsync(new Uri("geeksms:ShowArticle?ArticleId=100"));

LaunchFileAsync nos permite realizar la misma operación, pero con un archivo. Pasando como parámetro la instancia del archivo contenida en un IStorageFolder. Por ejemplo, con un archivo que distribuyamos con la aplicación:

var file = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("file.geeksms");
await Windows.System.Launcher.LaunchFileAsync(file);

Conclusión

Y esto es todo por hoy. Hemos visto como asociar nuestra aplicación a tipos de archivos y protocolos y como lanzar desde nuestra aplicación, otras aplicaciones usando archivos y URLs. Como siempre, a continuación tenéis tres proyectos con los ejemplos que hemos desarrollado en el post para que juguéis con ellos.

Un saludo y Happy Coding!

[Libro] Desarrollo en Windows 8 y Windows Phone 8 con XAML y C#

Desarrollo-Windows-8-350

Hola a todos!

Hoy es un gran día para mi!!

Por fin está aquí el libro completo en el que, junto a Ibon Landa y Rafa Serna, he estado trabajando los últimos seis meses. Algunos ya pudisteis adquirir una preview del mismo. Pues ahora tenéis más para leer. La preview tenía unas 190 páginas y 5 capítulos,el definitivo, tiene 550 páginas, 12 capítulos y un apéndice. ¿Qué encontraremos que no estuviese en el anterior? Muchas cosas:

  • Buenas practicas en la implementación de MVVM: Inyección de dependencias, Servicios, Locator…
  • Pruebas unitarias para ambas plataformas
  • Ciclo de vida de aplicaciones
  • Acceso al sistema:
    • contratos
    • lanzadores
    • selectores
    • aplicaciones lenses
    • extensibilidad de búsqueda
    • uso de voz en wp8
    • acceso a la cámara…
  • Sensores:
    • GPS
    • nuevos mapas de Nokia
    • Acelerómetro
    • Brújula
    • Giroscopio…
  • NFC y Bluetooth
  • Tiles y notificaciones
  • Comunicaciones
  • Windows Azure

Y además de esto, todo lo que ya tenía el anterior libro de introducción. En total tendremos unas 360 páginas totalmente nuevas, casi el triple de páginas de la preview en total. Todo ello, aderezado con más de 120 ejemplos para ambas plataformas!

Espero que disfrutéis tanto leyéndolo como yo he disfrutado escribiéndolo! Podéis ver el índice completo, y comprarlo si os gusta, aquí

BONUS: Entre los compradores del libro, se sorteará un kit de desarrollo de Nokia, compuesto por un Lumia 820 y una cuenta Nokia Premium Developer Program: más información aquí

Un saludo y Happy Coding!

Windows Phone 8: Gestión de contactos

Hola a todos!

Hasta ahora, cualquier aplicación podía hacer uso del lanzador SaveContactTask para añadir un contacto al teléfono. Lo malo de este lanzador es que requiere intervención del usuario, por lo que está muy bien para guardar un contacto pero es inusable para guardar muchos más.

En Windows Phone 8 una de las novedades del sistema es el poder integrar aplicaciones VoIP con el sistema. Para ello estas aplicaciones pueden publicar la lista de contactos en el people hub. Pero esta característica no es única de las aplicaciones VoIP, cualquier aplicación puede crear su propia “Store” dentro del people hub y añadir contactos a ella de forma automática. Para ello haremos uso del namespace Windows.Phone.PersonalInformation.

Almacén de contactos

En este namespace Windows.Phone.PersonalInformation encontramos la clase ContactStore. Esta clase representa el almacén de contactos de nuestra aplicación. Si observamos como funciona el people hub en Windows Phone, veremos que todo contacto está asociado a un almacen: twitter, linkedin, facebook… Bien, con la clase ContactStore podremos acceder o crear el almacén de nuestra aplicación e indicar como se relaciona con el resto del sistema, usando el método CreateOrOpenAsync:

public async Task CreateContactsStoreForApplication()
{
    store = await ContactStore.CreateOrOpenAsync(ContactStoreSystemAccessMode.ReadWrite, 
                                                 ContactStoreApplicationAccessMode.ReadOnly);
}

Este método recibe dos parámetros: access y sharing. El primero, access, indica la forma en la que el sistema se relaciona con nuestro almacén. Podemos escoger en el enumerador ContactStoreSystemAccessMode entre dos valores: ReadOnly, el sistema solo puede leer los contactos y no se pueden editar o ReadWrite, el sistema puede modificar los contactos de esta ContactStore. El segundo parámetro, sharing, indica como se relacionan otras aplicaciones con nuestros contactos. Podemos escoger en el enumerador ContactStoreApplicationAccessMode entre dos valores: LimitedReadOnly, donde el resto de aplicaciones solo podrán obtener la imagen del contacto y su descripción, o ReadOnly, con la que otras aplicaciones podrán leer todas las propiedades de un contacto en nuestra Store.

En ningún caso indicamos el nombre del almacén, este es inferido directamente de nuestra aplicación.

Añadir contactos al almacén

Una vez que hemos creado o abierto nuestra ContactStore, podremos empezar a trabajar añadiendo contactos, consultando los ya creados o eliminando los que deseemos. Para crear o actualizar contactos usaremos la clase StoredContact que nos permitirá indicar los valores de las propiedades standard del contacto como pueden ser: nombre, email, etc…:

public async Task AddContactToStore(string name, string phone, string email, string alterEgo)
{
    StoredContact contact = new StoredContact(store);

    contact.RemoteId = Guid.NewGuid().ToString();
    contact.DisplayName = name;

    var contactProperties = await contact.GetPropertiesAsync();

    contactProperties[KnownContactProperties.Telephone] = phone;
    contactProperties[KnownContactProperties.Email] = email;

    ...
}

Siempre que creemos una nueva instancia de esta clase, tendremos que indicar una instancia de ContactStore válida, que usará internamente. Tras crear la instancia, existe una propiedad en especial muy importante: RemoteId. Esta identifica de forma única a nuestro contacto y no puede repetirse. En este caso le hemos indicado un GUID pero puedes usar cualquier cadena de texto siempre y cuando sea única. A continuación usamos el método GetPropertiesAsync, que nos devuelve un Dictionary<string, object> donde podremos indicar las propiedades del contacto, tales como teléfono, email, cumpleaños, compañía… para ello, usaremos el enumerado KnownContactProperties y así evitar el uso de “magic strings” que puedan producir errores. Pero, ¿Qué ocurre si nosotros tenemos campos que no coinciden con los del enumerado? ¿Estamos restringidos solo a usar estos y descartar el resto de información? No, para nada. Al igual que tenemos el método GetPropertiesAsync, disponemos de otro llamado GetExtendedPropertiesAsync este método nos permite obtener las propiedades “extras” que tiene un contacto, las no incluidas en la lista de propiedades conocidas. Se trata de un Dictionary<string, object> en el que podremos incluir cualquier propiedad que deseemos:

var extendedProperties = await contact.GetExtendedPropertiesAsync();

extendedProperties["AlterEgo"] = alterEgo;
extendedProperties["Home"] = "cave";

CONSEJO: Aunque de cara al ejemplo hemos usado “magic strings” para las claves, lo recomendable sería crear un enumerado, llamado por ejemplo ExtendedKnownProperties, que contenga todos los campos que necesitemos.

Una vez que hayamos terminado de añadir todos los campos extra que deseemos, es momento de guardar nuestro contacto. Para ello usaremos el método SaveAsync de la clase StoredContact, a continuación el código completo de nuestro método AddContactToStore:

public async Task AddContactToStore(string name, string phone, string email, string alterEgo)
{
    StoredContact contact = new StoredContact(store);

    contact.RemoteId = Guid.NewGuid().ToString();
    contact.DisplayName = name;

    var contactProperties = await contact.GetPropertiesAsync();

    contactProperties[KnownContactProperties.Telephone] = phone;
    contactProperties[KnownContactProperties.Email] = email;

    var extendedProperties = await contact.GetExtendedPropertiesAsync();

    extendedProperties["AlterEgo"] = alterEgo;
    extendedProperties["Home"] = "cave";


    await contact.SaveAsync();
}

En el ejemplo que he preparado, todo este código está aislado en un servicio llamado ContactsService que inyectamos en nuestra ViewModel, de forma que podamos invocarlo desde un comando:

public async void AddContactCommandExecute()
{
    this.IsBusy = true;

    await this.contactsService.CreateContactsStoreForApplication();
    await this.contactsService.AddContactToStore("Bruce Wayne", "555-123123", "bat@darkness.com", "Batman");

    this.IsBusy = false;
}

RECUERDA: Antes de poder ejecutar nuestra aplicación, tenemos que recordar añadir a las capacidades del manifiesto el uso de contactos (ID_CAP_CONTACTS) o fallará al intentar acceder a la ContactStore.

Si ejecutamos la aplicación ahora y presionamos el botón “Add a new contact to phone” se ejecutará el código que crea la store si no existe y añade el contacto. Después podemos ir al people hub y ver como aparece nuestra aplicación en la lista de proveedores de contactos y el contacto que hemos creado en la lista de contactos:

image

Consultar contactos del almacén

Bien, ya hemos visto como podemos crear nuestro propio almacén de contactos en el dispositivo y como añadir contactos al mismo. Ahora veamos como consultar esos contactos que hemos creado. Para ello usaremos el método CreateContactQuery de la clase ContactStore, a la que podemos pasarle una instancia de la clase ContactQueryOptions indicando la ordenación y los campos incluidos en la consulta:

ContactQueryOptions options = new ContactQueryOptions();

options.OrderBy = ContactQueryResultOrdering.FamilyNameGivenName;
options.DesiredFields.Add(KnownContactProperties.Email);
options.DesiredFields.Add(KnownContactProperties.GivenName);

ContactQueryResult query = store.CreateContactQuery(options);

En este caso, queremos que se ordenen los contactos por los apellidos y después por el nombre. También indicamos que deseamos incluir propiedades como el Email y el GivenName. a continuación ejecutamos el método CreateContactQuery con estas opciones y obtenemos nuestra consulta lista para ser ejecutada en la nueva instancia de ContactQueryResult. En este punto todavía no tenemos datos y no se ha realizado ninguna consulta, simplemente hemos preparado el “terreno”.

Ahora tenemos dos métodos en el objeto ContactQueryResult: GetContactsAsync, que nos devuelve una lista de contactos, y GetContactCountAsync, que nos indica el número de contactos en este almacén. Como podemos ver, no podemos realizar ningún tipo de filtrado en este punto. Llamamos al método GetContactsAsync y luego trabajamos con la lista de contactos devuelta, quedando nuestro método QueryContactByName de la siguiente forma:

public async Task<StoredContact> QueryContactByName(string name)
{
    ContactQueryOptions options = new ContactQueryOptions();

    options.OrderBy = ContactQueryResultOrdering.FamilyNameGivenName;
    options.DesiredFields.Add(KnownContactProperties.Email);
    options.DesiredFields.Add(KnownContactProperties.GivenName);

    ContactQueryResult query = store.CreateContactQuery(options);

    var contactList = await query.GetContactsAsync();

    return contactList.Where(c => c.DisplayName == name).First();
}

TRUCO: Si queremos que la interfaz de nuestro servicio (IContactsService) y nuestra ViewModel sean compatibles con Windows Phone 7 o Windows 8, es recomendable crearnos una clase que contenga los campos del contacto que necesitemos (dto o poco), en vez de devolver un StoredContact, así no tendremos problemas a la hora de implementar este servicio en otras plataformas.

Eliminar contactos del almacén

Ahora que sabemos como consultar un contacto y leerlo desde el almacén del dispositivo, veamos como eliminarlo. Esto es realmente sencillo, además nos apoyaremos en el método QueryContactByName que hemos creado anteriormente. Para eliminar un contacto usaremos el método DeleteContactAsync de la instancia de ContactStore que tenemos activa. A este método necesitamos indicarle el RemoteId del contacto, por lo que primero tendremos que buscar el contacto, si no lo tenemos ya:

public async Task DeleteContact(string name)
{
    var contactToDelete = await QueryContactByName(name);

    await store.DeleteContactAsync(contactToDelete.RemoteId);
}

¿Sencillo verdad? Si revisamos el código que hemos incluido en este artículo, veremos que no hay ninguna parte especialmente complicada. Clases con nombres muy descriptivos, métodos sencillos y directos. Creo que uno de los grandes éxitos de la gestión de contactos, y del API de Windows Phone 8 en general, es la sencillez de su uso.

Conclusión

Bueno, llegamos al fin. Esta es una de las características nuevas de Windows Phone 8 que habíamos pedido durante mucho tiempo los desarrolladores. Ahora es momento de demostrar las cosas increíbles que podemos hacer con los contactos! Como siempre, a continuación os dejo el código de ejemplo que hemos visto, perfectamente funcionando en un proyecto de Windows Phone 8, con MVVM, con Autofac, Comandos y todo bien comentado y en su sitio!!.

Un saludo y Happy Coding!