Windows Azure Mobile Services is a awesome product that will help you to deliver great apps using a Windows Azure backend.
One of the feature I like is the possibility to send push notifications to modify tiles in just few lines of code. There is just a small drawback to this technique: as the queue notification must be enable from the client code, you won’t be able to get animated tiles.
But (as there is always a “but” ;), the next part of this post will provide you a simple but useful technique to get animated tiles for you Windows Store app.
In fact, instead of of send a classic notification, we’ll use the sendRaw method of the wns object to send a Raw notification. As a reminder, a raw notification is a notification that’s not displayed on the screen: as a developer, your code is notified that the notification is coming and you have to perform what you want/need.
So first, you have to create your server script (here is mine as an example):
var LASTEST_QUOTES_NUMBER = 5;
var payload;
var idx;
function cleanChannels(callback) {
var channelsTable = tables.getTable('Channel');
channelsTable.read({
success: function(channels) {
if(channels.length > 0) {
channels.forEach(function(channel){
if(channel.expirationTime < new Date()){
channelsTable.del(channel.id);
}
})
}
if(callback) {
callback();
}
}
})
}
function preparePayload(movie, quote){
payload += "<QuoteWithMovie>" +
"<Movie Title=\"" + movie.title + "\" FrontCover=\"" + movie.poster_path + "\" BackCover=\"" + movie.backdrop_path + "\" />" +
"<Quote Text=\"" + quote.text.substring(100) + "\" />" +
"</QuoteWithMovie>";
idx++;
if(idx == LASTEST_QUOTES_NUMBER) {
payload += "</Results>";
cleanChannels(function() {
var channelsTable = tables.getTable('Channel');
channelsTable.read({
success: function(channels) {
if(channels.length > 0) {
for (var i = 0; i < channels.length; i++) {
var channel = channels[i];
push.wns.sendRaw(channel.uri, payload);
}
}
}
});
});
}
}
function sendLatestQuotes() {
payload = "";
idx = 0;
var moviesTable = tables.getTable('Movie');
var quotesTable = tables.getTable('Quote');
quotesTable.where({ ispublished: true })
.orderBy('date')
.take(LASTEST_QUOTES_NUMBER)
.read({
success: function(latestQuotes) {
payload += "<Results>";
latestQuotes.forEach(function(quote) {
moviesTable.where({ movieid: quote.movieid })
.read({
success: function(movies) {
preparePayload(movies[0], quote);
}
})
})
}
})
}
As you can see, the code is pretty simple: a XML payload is construct from some database records and, when I have the number of notification I want (5 because notification queuing allow you to switch up to 5 notifications), I send a raw notification, using the payload constructed before.
On the client side, the code use the PushNotificationReceived event to be notified and, if a raw notification is coming, the code use the content (the XML payload) to update the tile:
var currentChannel = await PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync();
currentChannel.PushNotificationReceived += async (sender, args) =>
{
if (args.NotificationType == PushNotificationType.Raw && args.RawNotification != null)
{
await MainTileUpdateTask.UpdateMainTileAsync(args.RawNotification.Content);
// The raw notification should not be passed to its background task
args.Cancel = true;
}
};
The code to update the tile and animate it is dedicated to your application but, for you records, here is the one I’m using:
internal static async Task UpdateMainTile(string content)
{
_isUpdatingTile = true;
var tileUpdater = TileUpdateManager.CreateTileUpdaterForApplication();
tileUpdater.EnableNotificationQueue(true);
tileUpdater.Clear();
var xElement = XElement.Parse(content);
var quotesWithMovies = from q in xElement.Elements("QuoteWithMovie")
let movie = q.Element("Movie")
let quote = q.Element("Quote")
select new QuoteWithMovie
{
MovieTitle = movie.Attribute("Title").Value,
FrontCover = movie.Attribute("FrontCover").Value,
BackCover = movie.Attribute("BackCover").Value,
Text = quote.Attribute("Text").Value
};
var quoteWithMovies = quotesWithMovies as IList<QuoteWithMovie> ?? quotesWithMovies.ToList();
if (quoteWithMovies.Any())
{
for (int index = 0; index < quoteWithMovies.Count; index++)
{
var quote = quoteWithMovies[index];
var frontCoverFileFullUrl =
await MoviesService.Current.GetFullCoverImageUrlAsync(quote.FrontCover, "w154");
var backCoverFileFullUrl =
await MoviesService.Current.GetFullCoverImageUrlAsync(quote.BackCover, "w300");
var squareImageFilename = string.Format("Square_{0}.jpg", index);
var wideImageFilename = string.Format("Wide_{0}.jpg", index);
FileHelpers.DownloadFile(ApplicationData.Current.LocalFolder, squareImageFilename,
frontCoverFileFullUrl);
FileHelpers.DownloadFile(ApplicationData.Current.LocalFolder, wideImageFilename,
backCoverFileFullUrl);
var wideTile = Core.UI.Notifications.TileContentFactory.CreateTileWideImageAndText01();
wideTile.Branding = TileBranding.None;
wideTile.RequireSquareContent = false;
wideTile.Image.Src = string.Format("ms-appdata:///local/{0}", wideImageFilename);
wideTile.Image.Alt = quote.MovieTitle;
wideTile.TextCaptionWrap.Text = quote.Text;
var squareTile = Core.UI.Notifications.TileContentFactory.CreateTileSquareImage();
squareTile.Branding = TileBranding.None;
squareTile.Image.Src = string.Format("ms-appdata:///local/{0}", squareImageFilename);
squareTile.Image.Alt = quote.MovieTitle;
var wideTileNotification = wideTile.CreateNotification();
var squareTileNotification = squareTile.CreateNotification();
tileUpdater.Update(wideTileNotification);
tileUpdater.Update(squareTileNotification);
}
}
_isUpdatingTile = false;
}
The might be others techniques to perform the same results but I like this one for one reason: raw notification can be received by BackgroundTask!
So if you add a BackgroundTask (which set its trigger to PushNotificationTrigger) and put you server code in a Mobile Services Scheduler, you’ll be able to update the tile even if the app is not running!
Happy coding!