Windows Phone 7 y 8: Live Tiles

Llevaba mucho tiempo queriendo escribir un post sobre los live tiles de Windows phone (tanto para wp7 y wp8),

Pero he preferido esperar a la actualización de mi app ‘google analytics client’ para que podamos ver el código que he utilizado en la misma sobre los live tiles. Os animo a que si tenéis webs o apps con google analytics, os descarguéis la aplicación y podáis comprobar in situ el funcionamiento de los live tiles.

Me he basado en los posts de Javier Suárez Ruiz, Puntos NET, en los oficiales de Microsoft y en el de Nokia developer, con esto he podido dotar de versatilidad a mi aplicación en cuanto a los tan prácticos live tiles que windows phone nos brinda la posibilidad de utilizar de una manera muy cómoda y sencilla.
Mi aplicación utiliza el llamado Flip template (de los 3 posibles que tenemos a nuestra disposición, Flip, Iconic y Cycle templates).

No esperéis encontraros con un artículo teórico donde explique que es por ejemplo ‘reflexión’ o los live tiles en windows phone (para conocer que son estos conceptos podeis verlo en las webs que he utilizado como inspiración), el post es meramente práctico, por supuesto cualquier duda que tengáis estoy a vuestra disposición (y seguro que yo aprendo más que vosotros, así que adelante 🙂

Comencemos:

CREAR LIVE TILE
* en wp7 sólo tenemos un tamaño (mediano), en wp7.8 y wp8 tenemos hasta 3 tamaños para el Flip template – Small:159 × 159 pixels, Medium:336 × 336 pixels y Wide:691 × 336 pixels
* La idea es crear una imagen o texto, yo lo he hecho con imagen (que será lo que muestre en el live tile) con los datos que necesitemos, esto lo hacemos en la función crearLiveTile7 o crearLiveTile8
* esa imagen la guardamos en el espacio compartido que tenemos en el dispositivo (/Shared/ShellContent/)
* recuperamos la imagen guardada y generamos el live tile con esa imagen, esto lo hacemos en la función generarLiveTile7 ó generarLiveTile8

ACTUALIZAR LIVE TILE EXISTENTE
* Lo mismo que al crear, selecciono los datos a mostrar en el live tile, creamos una imagen o texto (en el ejemplo, he empleado una imagen)
* esa imagen la guardamos en el espacio compartido que tenemos en el dispositivo (/Shared/ShellContent/)
* recuperamos la imagen guardada y generamos el live tile con esa imagen, esto lo hacemos en la función generarLiveTile7 ó generarLiveTile8

Nota: Si desarrollamos para WP7.8 debemos editar el archivo de manifiesto WMAppManifest.xml, para ello en el explorador de soluciones hacemos clic derecho sobre el arhivo WMAppManifest.xml y elegimos la opción “Text Editor”. Justo debajo del elemento debemos añadir:

<AppExtra xmlns="" AppPlatformVersion="8.0">
    <Extra Name="Tiles"/>
</AppExtra>

Y ahora en el código de la aplicación añadimos:

//CREAR Y ACTUALIZAR LIVE TILE
//Tengo una función para conocer si el live tile lo creo para la versión 7.8 u 8 o para versiones previas
private static Version wp7Version = new Version(7, 10, 8858);

public static bool isWp8Version
{
	get
	{
		return Environment.OSVersion.Version >= wp7Version;
	}
}

if (isWp8Version)
{
	//llamamos a la función para el live tile para la versión 7.8 u 8
	crearLiveTile8Small();
	crearLiveTile8Medium();
	crearLiveTile8Wide();
}
else
{
	//llamamos a la función para el live tile para las versiones previas
	crearLiveTile7();
}

//WP7
private void crearLiveTile7()
{
	// generamos el front
	WriteableBitmap bmp = new WriteableBitmap(173, 173);

	bmp.Render(canvasFront, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/front.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 173, 173, 0, 100);
		}
	}

	// generamos el back
	bmp.Render(canvasBack, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/back.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 173, 173, 0, 100);
		}
	}

	// creamos el live tile
	generarLiveTile();
}

private void generarLiveTile7()
{
	StandardTileData tile = new StandardTileData();

	tile.BackgroundImage = new Uri("isostore:/Shared/ShellContent/front.jpg", UriKind.Absolute);
	tile.BackBackgroundImage = new Uri("isostore:/Shared/ShellContent/back.jpg", UriKind.Absolute);
	// le asignamos un nombre único para que podamos actualizar o eliminar el live tile
	string rutaView = "/Views/panDetallePerfil_LandPage.xaml?idLiveTile=1";

	//aquí diferenciamos si creamos o actualizamos el live tile
	//deberemos recogerlo en una variable
	if(bNuevoLiveTile)
	{
		ShellTile.Create(new Uri(rutaView, UriKind.Relative), tile);
	}
	else
	{
		ShellTile foundTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(rutaView));

		if (foundTile != null)
		{
			foundTile.Update(tile);
		}
	}
}

//WP8
private void crearLiveTile8Small()
{
	// generamos el front
	WriteableBitmap bmp = new WriteableBitmap(85, 85);

	bmp.Render(canvasFrontSmall, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/frontSmall.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 159, 159, 0, 100);
		}
	}

	bCreadoLiveTile1 = true;
	// creamos el live tile
	generarLiveTile8();
}

private void crearLiveTile8Medium()
{
	// generamos el front
	WriteableBitmap bmp = new WriteableBitmap(173, 173);

	bmp.Render(canvasFrontMedium, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/frontMedium.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 336, 336, 0, 100);
		}
	}

	// generamos el back
	bmp.Render(canvasBackMedium, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/backMedium.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 336, 336, 0, 100);
		}
	}

	bCreadoLiveTile2 = true;
	// creamos el live tile
	generarLiveTile8();
}

private void crearLiveTile8Wide()
{
	// generamos el front
	WriteableBitmap bmp = new WriteableBitmap(346, 173);

	bmp.Render(canvasFrontWide, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/frontWide.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 691, 336, 0, 100);
		}
	}

	// generamos el back
	bmp.Render(canvasBackWide, null);
	bmp.Invalidate();

	// lo guardamos en el shared
	using (var store = IsolatedStorageFile.GetUserStoreForApplication())
	{
		var filename = "/Shared/ShellContent/backWide.jpg";
		using (var st = new IsolatedStorageFileStream(filename, FileMode.Create, FileAccess.Write, store))
		{
			bmp.SaveJpeg(st, 691, 336, 0, 100);
		}
	}

	bCreadoLiveTile3 = true;
	// creamos el live tile
	generarLiveTile8();
}

private void generarLiveTile8()
{
	// preguntamos si ya tenemos valores para los tres tamaños
	if (bCreadoLiveTile1 && bCreadoLiveTile2 && bCreadoLiveTile3)
	{
		string rutaView = "/Views/panDetallePerfil_LandPage.xaml?idLiveTile=1";

		//aquí diferenciamos si creamos o actualizamos el live tile
		//deberemos recogerlo en una variable
		if(bNuevoLiveTile)
		{
			// primero creamos el live tile por el metodo tradicional
			// y despues mas abajo lo actualizamos mediante reflection
			ShellTile.Create(new Uri(rutaView, UriKind.Relative), new StandardTileData()
			{
				/*Title = "iconic tile",
				BackTitle = "iconic tile",
				BackContent = "iconic tile",
				BackgroundImage = new Uri("/Assets/logo202x202.png", UriKind.Relative),
				BackBackgroundImage = new Uri("/Assets/logo202x202.png", UriKind.Relative),
				Count = 8*/
			});
			var tileToUpdate = ShellTile.ActiveTiles.Last();
		}
		else
		{
			var tileToUpdate = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains(rutaView));
		}

		Type flipTileData = Type.GetType("Microsoft.Phone.Shell.FlipTileData, Microsoft.Phone");

		Type shellTile = Type.GetType("Microsoft.Phone.Shell.ShellTile, Microsoft.Phone");

		var UpdateTileData = flipTileData.GetConstructor(new Type[] { }).Invoke(null);

		//SetProperty(UpdateTileData, "Title", "Título");
		//SetProperty(UpdateTileData, "Count", 19);
		//SetProperty(UpdateTileData, "BackTitle", "Título Atrás");
		//SetProperty(UpdateTileData, "BackContent", "Contenido.");
		//SetProperty(UpdateTileData, "WideBackContent", "Contenido para el tile en tamaño ancho.");

		Uri smallBackgroundImage = new Uri("isostore:/Shared/ShellContent/frontSmall.jpg", UriKind.Absolute);
		Uri backgroundImage = new Uri("isostore:/Shared/ShellContent/frontMedium.jpg", UriKind.Absolute);
		Uri backBackgroundImage = new Uri("isostore:/Shared/ShellContent/backMedium.jpg", UriKind.Absolute);
		Uri wideBackgroundImage = new Uri("isostore:/Shared/ShellContent/frontWide.jpg", UriKind.Absolute);
		Uri wideBackBackgroundImage = new Uri("isostore:/Shared/ShellContent/backWide.jpg", UriKind.Absolute);

		SetProperty(UpdateTileData, "SmallBackgroundImage", smallBackgroundImage);
		SetProperty(UpdateTileData, "BackgroundImage", backgroundImage);
		SetProperty(UpdateTileData, "BackBackgroundImage", backBackgroundImage);/*opcional*/
		SetProperty(UpdateTileData, "WideBackgroundImage", wideBackgroundImage);
		SetProperty(UpdateTileData, "WideBackBackgroundImage", wideBackBackgroundImage);/*opcional*/

		shellTile.GetMethod("Update").Invoke(tileToUpdate, new Object[] { UpdateTileData });
	}
}

private static void SetProperty(object instance, string property, object value)
{
	var method = instance.GetType().GetProperty(property).GetSetMethod();
	method.Invoke(instance, new object[] { value });
}

Y este será el resultado que obtendremos:

2013-07-03_093816 2013-07-03_093837 2013-07-03_093850

Google Analytics Client

Analytics client es un cliente de Google analytics con el que vas a
poder conocer en tiempo real (siempre y cuando estén disponibles en Google
analytics) el uso que están haciendo los usuarios de tu web o aplicación
móvil.

Analytics client tiene un panel que es totalmente personalizable,
donde podrás seleccionar que métricas deseas ver según las necesidades que
tengas. También podrás seleccionar si al iniciar la aplicación quieres
actualizar las métricas de todos tus perfiles o prefieres hacerlo
manualmente. En este panel y para cada perfil por separado, podrás
actualizar los datos, ver el detalle del perfil para acceder a más métricas,
ver eventos y crear el live tile.

* Asimismo, podrás ver estadísticas detalladas de cada perfil, datos
demográficos, eventos, gráficas.
* La aplicación es multiusuario por lo que puedes cambiar entre usuarios
rápidamente sin necesidad de andar introduciendo contraseñas.
* Podrás anclar al inicio y ver estadísticas desde la pantalla de inicio de
tu dispositivo. Tienes 2 maneras de crear el live tile, bien accediendo
directamente al detalle del perfil, bien actualizando los datos en el propio
live tile. Podrás tener tantos live tiles como perfiles tengas, incluso
varios live tile por perfil dependiendo de la dimensión temporal que quieras
(día, mes, hora…), y en cada live tile podrás ver las métricas que
quieras!
* Podrás elegir entre diferentes dimensiones temporales para ver las
estadísticas de tu sitio web o aplicación móvil.
* Podrás ver eventos para conocer que acciones llevan a cabo los usuarios en
tu página.

La contraseña de analytics, necesaria para obtener el código de autorización
de Google no se guarda en la aplicación.
Esta aplicación en ningún caso hará uso de los datos introducidos con fines
comerciales ni promociónales.

En definitiva, se trata de una aplicación enfocada a conocer en tiempo real
el uso que los usuarios hacen de tu web o aplicación móvil.

Espero que la disfrutes.

Google Analytics Client