Sunday 20 January 2013

M25: My First WP8 App - Part V

One of the areas which gave me some grief was the ScheduledTaskAgent.  It was important the the app periodically refresh its data and update the Live Tile.  Setting this up is pretty simple, however, getting it to behave proper was another.

You can think of ScheduledTaskAgents like a mini-Windows Service.  Once a ScheduledTaskAgent is  registered the OS will run it every approximately every 30 minutes allowing it to do its business and you have 10 seconds to do your thing.  This is done through the ScheduledTaskAgent.OnInvoke method.  With my app, my ScheduledTaskAgent needed to re-fetch all of the latest traffic data, process it and update any Routes (if defined).

Within my M25.Data component I have a RefreshTrafficData method which is marked with the new async keyword.  This was done to allow the WP UI to retain control while the DataManager was busy fetching and processing new data.  The DataManager fires appropriate events, such as DataTransferStarted and DataRefreshed, to allow the UI to display a progress bar.

While all this works wonderfully from the WP app, calling this method from the ScheduledTaskAgent is another matter.  When refreshing traffic data we don't want this to run asynchronously, since we need to take the result of the refresh and update our Live Tile with the new data.

To make a long story short, I ended up creating a new method in the DataManager which effectively was a wrapper around RefreshTrafficData, but which returned a Task object.  By returning the Task object I can this call the Task.Wait method from within the ScheduledTaskAgent.OnInvoke.  Execution will pause until completed and proceed to call the NotifyComplete method signalling the end of our operation.

There are a couple of extension methods which I found/created which were beneficial.  The first method addresses the fact that in WP8 WebClient.DownloadString has been removed and replaced with the asynchronous DownloadStringAsync (which fires the DownloadStringComplete event with the result).

The DownloadStringTask extension method allows you to call DownloadStringAsync synchronously, if needed.

public static Task DownloadStringTask(this WebClient webClient, Uri uri)
        {
            var taskCompletionSource = new TaskCompletionSource();
            webClient.DownloadStringCompleted += (s, e) =>
                      {
                         if (e.Error != null)
                         {
                             taskCompletionSource.SetException(e.Error);
                         }
                         else
                         {
                             taskCompletionSource.SetResult(e.Result);
                         }
                      };

            webClient.DownloadStringAsync(uri);
            return taskCompletionSource.Task;
        }

This allows you to do this:

Task data = new WebClient().DownloadStringTask(new Uri(SOME_URL));
await data;

if (data.Exception == null && !string.IsNullOrEmpty(data.Result))
{
     ProcessResults(data.Result);
}

The other extension method relates to setting the BackContent of a Live Tile.  WP8 introduces a new large size to Live Tiles, so now the user can pick between small, medium and large tile sizes.  In most cases you will want to display dynamic information on the back of your tile.  But, there is no way of know which size the user has selected for your Live Tile.  Forget about the small size, since it does not permit back content.  Unfortunately for medium and large sizes the text WP does not provide automatic wrapping of text, so you will have to do it yourself.

Per MS documentation, the WideBackContent takes 27 characters, while the BackContent takes 13.  The
GetTitleLines extension method allows you to parse your Live Tile data into lines of text to accomodate both WideBackContent and BackContent.


  public static List GetTitleLines(this string theString, int wordCount)
        {
            string[] parts = theString.Split(' ');

            var lines = new List();

            string line = string.Empty;
            int index = 0;
            foreach (string part in parts)
            {
                if (!string.IsNullOrEmpty(part))
                {
                    if (line.Length + part.Length + 1 <= wordCount)
                    {
                        line += part;
                        line += " ";
                    }
                    else
                    {
                        lines.Add(line);
                        line = part;
                        line += " ";
                    }
                }

                index++;

                if (index >= parts.Length)
                {
                    lines.Add(line);
                }
            }

            return lines;
        }

Here is how you can use it:

  private void SetLongData(string data)
        {
            List lines = data.GetTitleLines(27);

            if (lines.Count > 1)
            {
                foreach (string line in lines)
                {
                    LongData += line + "\n";
                }
            }
            else
            {
                LongData = lines[0];
            }
        }

        private void SetMediumData(string data)
        {
            List lines = data.GetTitleLines(13);

            if (lines.Count > 1)
            {
                foreach (string line in lines)
                {
                    MediumData += line + "\n";
                }
            }
            else
            {
                MediumData = lines[0];
            }
        }


In the next part in this series I will be wrapping things up.



No comments: