Notificaciones y Mosaicos en Windows Phone

Publicado en

Sin temor a equivocarme puedo decir que el principal distintivo de la plataforma Windows Phone son sus mosaicos; esos recuadros que no solo son accesos directos a aplicaciones sino que muestran información sin tener que acceder a la aplicación.

 En este artículo mostraré cómo implementar notificaciones en Windows Phone. Además de considerar las notificaciones de mosaico (tile), también mostraré como implementar notificaciones Toast (como por ejemplo las que se muestran al recibir un mensaje SMS) y notificaciones Raw (mensajes de texto simple).

El servicio de notificaciones

Lo primero que haremos será crear un servicio WCF que envíe las notificaciones. Para ello creamos un proyecto en Visual Studio de tipo “Aplicación de servicios WCF”, bautizando nuestro proyecto como Servicio Notificación. Veremos el archivo Service1.svc.cs, podemos abrirlo y eliminar los métodos GetData() y GetDataUsingDataContract() que esencialmente funcionan sólo como ejemplos.

&emps;El listado 1 muestra nuestro código inicial. Como comenté, hay tres tipos de notificaciones: Tile, Toast y Raw. Utilizaremos un enumerador público “IntervalosDeValores” para identificar el tipo de notificación. También creamos un diccionario estático “URISucripción” para registrar a los teléfonos suscritos a nuestro servicio. Como llave usamos un Guid (identificador global único) y como valor un Uri que establece un canal de comunicación entre el servicio web y el dispositivo.


public enum IntervalosDeValores {
 TileInmediato = 1,
 ToastInmediato = 2,
 RawInmediato = 3,
}

private static Dictionary URISuscripcion = new Dictionary();

Listado 1. Enumerador y diccionario

El listado 2 muestra el método para suscribir teléfonos al diccionario.


public void SuscribirMiTelefono(Guid IdTelefono, string canalURI) {
 Uri direccionLocal = new Uri(canalURI);
 if (URISuscripcion.ContainsKey(IdTelefono)) {
   URISuscripcion[IdTelefono] = direccionLocal;
 } else {
   URISuscripcion.Add(IdTelefono, direccionLocal);
 }
}

Listado 2. Suscribir teléfono

 El listado 3 muestra el código que usaremos para enviar notificaciones Raw, que simplemente son una cadena de texto. Las notificaciones deben enviarse como arreglos de bytes codificados en UTF8, así que utilizaremos la clase UTF8Encoding. Más adelante detallaremos el método InsertarInformacionURLSuscritas(), que utilizaremos en los 3 tipos de push.


public void InsertarInformacionRaw(string mensajeRaw) {
&emps;UTF8Encoding encoding = new UTF8Encoding();
 InsertarInformacionURLSuscritas(encoding.GetBytes(mensajeRaw), "raw");
}

Listado 3. Envío de notificaciones Raw

 El listado 4 muestra el código para insertar notificaciones toast. Estos mensajes manejan un título y un cuerpo. Para que el mensaje tenga la estructura requerida, lo formamos como XML usando la clase XMLWriter.


public void InsertaToast(string tituloToast, string mensajeToast) {
 MemoryStream miStream = new MemoryStream();
 XmlWriter escritorXML = XmlWriter.Create(miStream);
 escritorXML.WriteStartDocument();
 escritorXML.WriteStartElement("wp", "Notification", "WPNotification");
 escritorXML.WriteStartElement("wp", "Toast", "WPNotification");
 escritorXML.WriteStartElement("wp", "Text1", "WPNotification");
 escritorXML.WriteValue(tituloToast);
 escritorXML.WriteEndElement();
 escritorXML.WriteStartElement("wp", "Text2", "WPNotification");
 escritorXML.WriteValue(mensajeToast);
 escritorXML.WriteEndElement();
 escritorXML.WriteEndElement();
 escritorXML.WriteEndDocument();
 escritorXML.Flush();
 InsertarInformacionURLSuscritas(miStream.ToArray(), "toast");
}

Listado 4. Envío de notificaciones Toast.

 El listado 5 muestra el código para enviar notificaciones tipo “Tile”, es decir las que actualizan la información que se muestra en un mosaico. Hay tres diferentes elementos que se pueden enviar en una notificación tile: título, imagen y contador. El procedimiento es similar al que utilizamos en nuestro método para insertar mensajes Toast.


public void InsertaInformacionTile(string tituloTile, int contadorTile, string direccionImagenTile) {
 MemoryStream miStream = new MemoryStream();
 XmlWriter escritorXML = XmlWriter.Create(miStream);
 escritorXML.WriteStartDocument();
 escritorXML.WriteStartElement("wp", "Notification", "WPNotification");
 escritorXML.WriteStartElement("wp", "Tile", "WPNotification");
 escritorXML.WriteStartElement("wp", "BackgroundImage", "WPNotification");
 escritorXML.WriteValue(direccionImagenTile);
 escritorXML.WriteEndElement();
 escritorXML.WriteStartElement("wp", "Count", "WPNotification");
 escritorXML.WriteValue(contadorTile);
 escritorXML.WriteEndElement();
 escritorXML.WriteStartElement("wp", "Title", "WPNotification");
 escritorXML.WriteValue(tituloTile);
 escritorXML.WriteEndElement();
 escritorXML.WriteEndElement();
 escritorXML.WriteEndDocument();
 escritorXML.Flush();
 InsertarInformacionURLSuscritas(miStream.ToArray(), "tile");
}

Listado 5. Enviar información a un mosaico

 Como ya notamos anteriormente, todos los métodos terminan invocando InsertarInformacionURLSuscritas(). Este método se encarga de distribuir la información deseada hacia los dispositivos suscritos. El listado 6 muestra el código de este método.

A grandes rasgos, lo que hace este método es 1) crear una solicitud de tipo HttpWebRequest, 2) agregarle el header correspondiente al tipo de notificación, 3) escribir la información deseada y enviarla, 4) Recibir la respuesta. Esto se repite dentro de un ciclo por cada dispositivo suscrito.


private static void InsertarInformacionURLSuscritas(byte[] informacionRecibida, string tipoNotificacion) {
 foreach (var direccion in URISuscripcion.Values) {
  var solicitud = (HttpWebRequest)WebRequest.Create(direccion);
  solicitud.Method = WebRequestMethods.Http.Post;
  solicitud.ContentType = "text/xml";
  solicitud.ContentLength = informacionRecibida.Length;
  solicitud.Headers.Add("X-MessageID", Guid.NewGuid().ToString());
  switch (tipoNotificacion) {
   case "toast":
    solicitud.Headers["X-WindowsPhone-Target"] = "toast";
    solicitud.Headers.Add("X-NotificationClass",
     ((int)IntervalosDeValores.ToastInmediato).ToString());
    break;
   case "tile":
    solicitud.Headers["X-WindowsPhone-Target"] = "token";
    solicitud.Headers.Add("X-NotificationClass",
     ((int)IntervalosDeValores.TileInmediato).ToString());
    break;
   case "raw":
    solicitud.Headers.Add("X-NotificationClass",
     ((int)IntervalosDeValores.RawInmediato).ToString());
    break;
  }
  using (var streamSolicitud = solicitud.GetRequestStream()) {
   streamSolicitud.Write( informacionRecibida, 0,
    informacionRecibida.Length);
  }
  var respuesta = (HttpWebResponse)solicitud.GetResponse();
 }
}

Listado 6. Envío de solicitud

Habiendo implementado la lógica de nuestro web service, vamos a definir los contratos necesarios para que los métodos puedan ser accesibles por clientes externos. El listado 7 muestra el código que debe tener la clase IService.cs para definir los contratos de servicio correspondientes.


using System;
using System.ServiceModel;
namespace Servicio_Notificacion {
 [ServiceContract]
 public interface IService1 {
  [OperationContract],
  void SuscribirMiTelefono(Guid phoneID, string channelURI);
  [OperationContract]
  void InsertarInformacionRaw(string message);
  [OperationContract]
  void InsertaInformacionTile(string tileTitle, int tileCount, string tileImageURI);
  [OperationContract]
  void InsertaToast(string ToastTitle, string ToastMessage);
 }
}

Listado 7. Contrato de servicios

 Una vez hecho esto, podemos compilar nuestro proyecto y publicarlo. Luego podemos verificar que esté accesible utilizando el cliente de pruebas WcfTestClient (C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\WcfTestClient.exe). Seleccionamos la opción “Agregar servicio” del menú Archivo y alimentamos la referencia a nuestro servicio WCF.

La aplicación Windows Phone

Habiendo creado nuestro servicio, ahora crearemos la app para Windows Phone. Para ello creamos una nueva solución Windows Phone en Visual Studio y agregamos la referencia al servicio WCF. Para ello damos un clic derecho sobre el proyecto y seleccionamos la opción “Agregar referencia de servicio”. En la dirección, usamos la dirección IP local de nuestra máquina y el nombre del servicio que creamos en la sección anterior. La figura 1 muestra el ejemplo de cómo se debe ver esta ventana.

Figura 1. Agregar referencia de servicio

 Para desplegar la información en nuestra app, en la página principal de ésta colocaremos tres cajas de texto que desplieguen la información recibida. El código para esto se ve en el listado 8.


<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
 <TextBlock x:Name="txtEstadoSuscripcion" Margin="0,0,0,569" Text="Verificando estado de la suscripción"/>
 <TextBlock x:Name="txtMensajeRaw" Margin="0,70,0,499" Text="Esperando a recibir una notificación Raw" TextWrapping="Wrap"/>
 <TextBlock x:Name="txtCanalUri" Margin="0,140,0,0" Text="Esta es la dirección del canal:" TextWrapping="Wrap"/>
</Grid>

Listado 8. Cajas de texto para desplegar información

 Pasemos ahora al código C#. Por simplicidad, pondremos en nuestro constructor el código para disparar los eventos. El listado 9 muestra el código para el constructor. Estas acciones también se podrían disparar desde el método


OnNavigatedTo().
Guid phoneID;
ServicioNotificaciones.Service1Client miCliente = new ServicioNotificaciones.Service1Client();
HttpNotificationChannel miCanalPush;
public MainPage() {
 InitializeComponent();
 if(IsolatedStorageSettings.ApplicationSettings.Contains("IdDispositivo")) {
  phoneID =
   (Guid)IsolatedStorageSettings.ApplicationSettings["IdDispositivo"];
 } else {
  phoneID = Guid.NewGuid();
  IsolatedStorageSettings.ApplicationSettings["IdDispositivo"] = phoneID;
 }
 miCanalPush = HttpNotificationChannel.Find("miCanal");
 if (miCanalPush == null) {
  txtMensajeRaw.Text += "Canal nulo";
  miCanalPush = new HttpNotificationChannel("miCanal");
  adjuntarFuncionesManejadores();
  miCanalPush.Open();
 } else {
  txtMensajeRaw.Text += "¡Canal encontrado!";
  adjuntarFuncionesManejadores();
  miCliente.SuscribirMiTelefonoAsync(phoneID,
   miCanalPush.ChannelUri.ToString());
}
}

Listado 9. Constructor de la aplicación Windows Phone

 Analicemos el código del listado 9. Primero creamos unos objetos de tipo Service1Client (esta clase viene del servicio web que creamos) y HttpNotificationChannel. Verificamos que nuestra aplicación contenga un identificador único guardado en el almacenamiento aislado —y si no, lo creamos. Luego obtenemos el valor del canal que utilizaremos —si es nulo creamos una nueva instancia del canal y lo abrimos— e invocamos la operación SuscribirMiTelefono() del servicio web.

 En el listado 9 habrán notado llamadas a adjuntarFuncionesManejadores(). Este método define cuatro manejadores de eventos: ErrorOcurred que se dispara en caso de que algún error haya ocurrido con la comunicación por medio del canal; SuscribirMiTelefonoCompleted que se dispara cuando la suscripción del teléfono al servicio de notificaciones haya sido exitosa, el ChannelUriUpdated que se dispara cuando la dirección del canal haya sido obtenida (o actualizada); yHttpNotificationReceived que se dispara cuando la aplicación reciba información pero sin ser formateada. El listado 10 muestra el código de este método.


void adjuntarFuncionesManejadores() {
 miCanalPush.ErrorOccurred += myPushChannel_ErrorOccurred;
 miCliente.SuscribirMiTelefonoCompleted += miCliente_SuscribirMiTelefonoCompleted;
 miCanalPush.ChannelUriUpdated += myPushChannel_ChannelUriUpdated;
 miCanalPush.HttpNotificationReceived += myPushChannel_HttpNotificationReceived;
}

Listado 10. Definición de manejadores

El listado 11 tiene el manejador para el evento miCanalPush.ErrorOcurred. Este simplemente muestra en pantalla el mensaje de error.


void myPushChannel_ErrorOccurred(object sender, NotificationChannelErrorEventArgs e) {
 txtEstadoSuscripcion.Text = e.Message;
}

Listado 11. Manejo de error

El listado 12 tiene el manejador del evento miClient.SuscribirMiTelefonoCompleted. Consiste en desplegar un mensaje de error o éxito según sea el caso, así como el contenido del canal de comunicación utilizado.


void miCliente_SuscribirMiTelefonoCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) {
 if (e.Error == null) {
  txtEstadoSuscripcion.Text = "¡Suscrito!";
 } else {
  txtEstadoSuscripcion.Text = e.Error.Message;
 }
 txtCanalUri.Text = miCanalPush.ChannelUri.ToString();
}

Listado 12. Manejo de suscripción

El listado 13 tiene el código para el manejador miCanalPush.ChannelUriUpdated. Lo primero que hacemos es usar la propiedad IsShellTileBound para verificar si el canal de suscripción se encuentra enlazado a alguna notificación de mosaico. Si no se encuentra enlazado, enlazamos por medio del método BindToShellTile() de miCanalPush enviando una lista de dominios o IPs permitidos para el consumo de datos. De manera similar, verificamos si el canal de suscripción se encuentra enlazado a una notificación tipo Toast, y si no es así lo enlazamos. Finalmente, desplegamos un mensaje de estatus (“uri actualizada”) y ejecutamos el método SuscribirMiTelefonoAsync() del cliente.


void myPushChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e) {
 if (miCanalPush.IsShellTileBound == false) {
  var ListOfAllowedDomains = new Collection<Uri> {
   new Uri(@"http://192.168.0.46")
  };
  miCanalPush.BindToShellTile(ListOfAllowedDomains);
 }
 if (miCanalPush.IsShellToastBound == false) {
  miCanalPush.BindToShellToast();
 }
 txtMensajeRaw.Text = "uri actualizada";
 miCliente.SuscribirMiTelefonoAsync(phoneID,
  miCanalPush.ChannelUri.ToString());
}

Listado 13. Manejo de actualización de canal

 El listado 14 muestra el manejador de eventos para HttpNotificationReceived. Consiste en recibir el cuerpo del mensaje en un objeto de tipo StreamReader y desplegarlo en pantalla.


void myPushChannel_HttpNotificationReceived(object sender, HttpNotificationEventArgs e) {
 Deployment.Current.Dispatcher.BeginInvoke(() => {
  StreamReader miLector = new StreamReader(e.Notification.Body);
  txtMensajeRaw.Text =
   "Notificación RAW recibida: " + miLector.ReadToEnd();
 });
}

Listado 14. Manejo de HttpNotificationReceived

Juntando ambas partes

Una vez que tenemos el servicio web y la app de Windows Phone podemos verificar su funcionamiento.

Al desplegar la pantalla de inicio de nuestra aplicación, la aplicación marcará una excepción que explica un acceso no autorizado. Para solucionar este problema vamos a la pestaña de capacidades del archivo WMAppManifest y seleccionamos la opción ID_CAP_PUSH_NOTIFICATIONS. Con esto debe quedar resuelto el acceso. Ahora regresamos al cliente de pruebas del servicio WCF y ejecutamos la operación InsertarInformacionRaw(). Esto debe mostrarnos una pantalla en el teléfono indicando la recepción del mensaje, así como el contenido de este.

Como pueden ver, en el segundo renglón después de la leyenda Notificación RAW recibida se despliega el mensaje enviado.

Si ahora intentamos ejecutar la operación InsertaToast() enviando el título “notificacion” y el cuerpo “mensaje de prueba”, el dispositivo mostrará algo similar a la figura 2.

Figura 2. Despliegue de notificación toast

Para probar las notificaciones de mosaico vamos a colocar el mosaico de la aplicación que creamos en la pantalla de aplicaciones principales.

Después de hacerlo, ejecutamos la operación InsertarInformacionTile(). La figura 3 muestra un ejemplo de qué información incluir en los parámetros.

Figura 3. Parámetros al invocar InsertarInformacionTile

La imagen debe ser de 173x173 pixeles. La dirección de la imagen debe ser absoluta. En este caso yo la coloqué en el servidor web local de mi computadora. Para ahorrarse el trabajo de crear la imagen podemos recurrir al servicio de imágenes de lorem pixel. Simplemente usamos la dirección http://lorempixel.com/173/173

Figura 4. Notificación de mosaico

La figura 4 muestra un ejemplo de cómo se vería en el dispositivo una notificación de mosaico (imagen del ciclista con el título “Ejemplo Tile” y el contador 9).

Conclusión

Con esto terminamos este tutorial donde mostramos como crear notificaciones y desplegar notificaciones en Windows Phone. Este es un recurso muy útil y que le da gran interactividad a nuestras aplicaciones. Por otro lado, también debemos tener cuidado de no abusar de las notificaciones, ya que podemos terminar agobiando a nuestros usuarios.
Referencias

Referencias
  1. “Push notifications for Windows Phone”. MSDN. http://swgu.ru/r6
  2. “Sending push notifications for Windows Phone”. MSDN. http://swgu.ru/sg39r7
Bio

Amín Espinoza (@aminespinoza) es un desarrollador de software especializado en las plataformas Windows Phone, Windows 8 y Silverlight. Es un apasionado de la historia, y fan de las luchas, los comics, el café y la música. http://aminespinoza.com