Saturday, 20 September 2014

Background TimerTask 01

In my previous post we made a background timer task, a rather contrived example, but it got the idea across. Now let's continue with it but add some bells and whistles, let's say that we want to fetch some data from a web service, one thing we can guarantee is that for our background task to work we'll need an internet connection, otherwise it's senseless to fire our task, luckily we can easily add a condition to our task when we register it requiring an internet connection.

//Add internet condition
builder.AddCondition(new SystemCondition(SystemConditionType.InternetAvailable));

now that's nice, but once we fire our background task to fetch some data we probably want to do something with that data in our application, luckily we can also create a callback function to handle it, so let's do that, One important thing to note is that you can't create a callback function for an indefinite timer, that means for this to work you have to set the OneShot parameter to true otherwise your call back will never fire and you wont know why.

Change the Task trigger to be One Shot

//waits 15 minutes to schedual the background task, once scedualed it
//will fire within 15 minutes once
builder.SetTrigger(new TimeTrigger(15, true));

Add a callback to our task

taskRegistration.Completed += taskRegistration_Completed;

next create the callback, on the view I created a Text box  named Output so that I could display my results to the user. Now since there really is no way to directly pass a result back to your application from your background task i utilize the Application data store to do this.

private async void taskRegistration_Completed(BackgroundTaskRegistration sender,
    BackgroundTaskCompletedEventArgs args)
{
    await CoreApplication.MainView.CoreWindow.Dispatcher
                .RunAsync(CoreDispatcherPriority.Normal,
                        () => Output.Text = "Task Fired");

    if (sender.Name == "TimerTask")
        try
        {
            args.CheckResult();

            var localSettings = ApplicationData.Current.LocalSettings;
            var data = localSettings.Values["data"].ToString();

            await CoreApplication.MainView.CoreWindow.Dispatcher
                .RunAsync(CoreDispatcherPriority.Normal,
                            () => Output.Text = data);
        }
        catch (Exception ex)
        {
            var data = String.Format("{0} Error:{1}", Output.Text, ex.Message);

            CoreApplication.MainView.CoreWindow.Dispatcher
                .RunAsync(CoreDispatcherPriority.Normal,
                        () => Output.Text = data).AsTask().RunSynchronously();

        }

}

notice that we check our background task for errors, you never know when your connecting to external resources something could always go wrong and you should always check it. With our callback function complete lets modify our background task to pass back the current date time

using System;
using Windows.ApplicationModel.Background;
using Windows.UI.Notifications;

namespace pc.BackgroundTimerTask
{
    public sealed class TimerTask :IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            var d = taskInstance.GetDeferral();
            this.SendToast("Data Refreshed");
            string[] data = new string[1];
            
            //Deliberate Error
            //data[1] = DateTime.Now.ToString();
           
            var localSettings = Windows.Storage.ApplicationData.Current.LocalSettings;

            localSettings.Values["data"] = DateTime.Now.ToString();
           
            d.Complete();
        }

        private void SendToast(string Message)
        {
            var ToastXml = ToastNotificationManager
                .GetTemplateContent(ToastTemplateType.ToastText01);

            //extract all the text elements
            var toastTextElements = ToastXml.GetElementsByTagName("text");

            //set the one text element we have to a value
            toastTextElements[0].AppendChild(ToastXml.CreateTextNode(Message));

            //create the toast
            var Toast = new ToastNotification(ToastXml);

            //show the toast in the top right corner
            ToastNotificationManager.CreateToastNotifier().Show(Toast);

        }
    }
}

as you may notice I refactored our Background task a little bit, and pass my value back to my application using the Application data store. now I also, tried createing an exception within my application to try and handle it on the completed callback, but I haven't been able to figure this out, it seems that the background task throws my exception and the callback never fires.

now one thing to note is that our callback will only fire while our app is running, however a background task will fire whether the app is running, suspended or even terminated. so keep in mind that our TaskCompleted event will then fire next time we activate our application.

pretty cool eh?