[Windows 8] How to get animated Live Tiles with Windows Azure Mobile Services?

January 28th, 2013 | Posted by Tom in .NET | Article | Mobile Services | Windows 8 | WinRT | Read: 2,004

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!

You can follow any responses to this entry through the RSS 2.0 You can leave a response, or trackback.

2 Responses



Add Comment Register



Leave a Reply