Another cloud enabled WP7 Wx app ..                  Part Deux: The Live Tiles

Posted: June 28, 2011 in .NET, Mobile

This post is a continuation of Part 1 of the series (here).

So, we now have a basic Weather app that allows the user to look up live weather & a little forecast for his/her selected city. But, what good is that, unless we can pin the app to the start screen & then get live Weather feeds for the selected city, without having to launch the app! Now, sure we can enable that for Windows Phone using regular techniques of Push Notifications & Live Tiles. However, every time I show off something like that, the next obvious question is — What if I live in City A & work in City B and I want live weather updates for both cities? Or, if you are like me, you always keep Hawaii weather pinned so you feel good & look forward to your vacation 🙂 Now, there is something we could possibly work out using the Windows Phone 7.0 OS & toolsets. May be our cloud service could take in multiple cities & push out live weather for all cities in cycles? But, the weather app on the phone still has one pinned Live Tile and it is kinda cumbersome to display so much changing information in one Tile. Same problem crops up if you want to follow two live flights or numerous other situations. Hmmm…

Thankfully, Windows Phone Mango update has the near-perfect way of solving this issue. In addition to the pinned Application Tile (which is still just one & done manually by the user), we can now have multiple secondary Tiles for each app. These tiles are just like pinned Live Tiles on the start screen, but only better since they are created & controlled programmatically from the app itself! The windows phone team heard some feedback about the pain to stand up a service just to update a Live Tile when the app itself knows all the data; so now we can! And the best part — just as the Secondary Live Tile name suggests, each of these secondary Tiles behaves exactly like Application Tiles, have a unique URL identifier and most importantly, can be updated from the cloud using MPNS!! Voila .. now we can have multiple parts of the application pinning themselves to the start screen and receiving Live Tile updates through Push Notifications. This helps in situations like following weather of multiple cities, following multiple live flights or pinning various news sections for a news app. More detailed information about Application & Secondary Live Tiles can be found (here).

Apart from the programmatic control over secondary Live Tiles, the tiles themselves convey a little more information. In addition to the usual front of the Tile (with count, title & background image), the Tiles also flip to expose a back side of the tile. This has the potential for carrying some back-content text, title & another background image. So, information conveyed by the start screen Tiles can literally be doubled! And the OS makes sure the secondary tiles pinned from the same app don’t do their flip animations at the same time .. nice little trick.

So, back to our weather app. Now, each city the user cares about can be pinned to the start screen, providing live weather feeds that are easy to follow, each being separate. Our little implementation looks like this:

Secondary Live WX Tiles with Temp

Secondary Live WX Tiles with Temp

Secondary Live WX Tiles with Conditions

Secondary Live WX Tiles with Conditions

 

 

 

 

 

 

 

 

 

 

 

 

You will notice that with the extra real estate offered by the back of the tile, we were able to split up the weather information: current weather image & temperature in the tile front and current conditions & city name in the back of the tile. You can, off course, design your secondary live tiles to carry any information just the way you want. Here is some code to get the above working on clicking the “pin to start” menu as below:

Pin to Start Menu

Pin to Start Menu


   // Check the existence of pinned tile.
   var shellTile = ShellTile.ActiveTiles.FirstOrDefault(x => x.NavigationUri.ToString().Contains("WOEID=" + this.currentWOEID));

   if (shellTile != null)
   {
       return;
   }

   // Create the WX tile data with appropriate properties.
   var tileData = new StandardTileData
   {                
       BackgroundImage = this.currentWxImage,
       BackTitle = this.currentCity,
       Count = this.currentTemp,                
       BackContent = this.currentWxCondition
   };

   // Create the secondary tile on start screen.
   ShellTile.Create(new Uri("/MainPage.xaml?WOEID=" + this.currentWOEID, UriKind.Relative), tileData); 

With the above code firing, our app is immediately deactivated & the user is taken to the start screen to see the secondary Live Tile. Now, every city that the user cares to store & check weather for is represented by an unique WOEID (as explained in Part 1 here). Now, for every secondary Live Tile that is pinned, there has to be a unique way to identify the Tile; so why not simply use the city’s unique WOEID for the tile as well?

You will notice that as we created the ShellTile programmatically, we gave it a unique URL — “/MainPage.xaml?WOEID=something”. This serves a nice dual purpose. One, it identifies each secondary Live Tile uniquely. And since we can check the existence of secondary tiles programmatically, we get to tweak our app accordingly. For example, if we find that a city is already pinned to the start (proven by the existence of a secondary Live Tile with the same WOEID parameter), our menu option should possibly say “unpin from start” so the user may delete following that city.

Another awesome effect of uniquely identifying our Tiles with URL parameters is that it gives us a way to respond specifically when the user launches our app by hitting one of these Live Tiles. If the user has 2 cities pinned to the start screen, launching the app by hitting the Live Tile for City A should take the user directly into weather details for City A, right? Yep, we can do that because the URL parameters are passed directly into the app as we navigate back to the URL-linked page. This provides the developer with a way to respond to page events and may be take appropriate action based on the URL parameters coming in. For example, in our case, the MainPage.xaml is the weather UI for a given city. Much like the page_load events in the ASP.NET webforms world, we get to respond and take unique actions based on incoming parameters. Here’s some code:


        protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            if (NavigationContext.QueryString.ContainsKey("WOEID"))
            {
                if ((App.Current as WeatherLiteForMango.App).LocationList.Count > 0)
                {
                    foreach (App.LocationStuff existingLoc in (App.Current as WeatherLiteForMango.App).LocationList)
                    {
                        if (existingLoc.WOEID == NavigationContext.QueryString["WOEID"])
                        {
                            locationNumber = (App.Current as WeatherLiteForMango.App).LocationList.IndexOf(existingLoc);
                            break;
                        }
                    }

                    // Fetch WX data & fill.
                    this.GetYahooWeatherAsync(Convert.ToInt32((App.Current as WeatherLiteForMango.App).LocationList[locationNumber].WOEID));
                }
            }
            else
            {
                if ((App.Current as WeatherLiteForMango.App).LocationList.Count > 0)
                {
                    // Load the last location in list.
                    locationNumber = (App.Current as WeatherLiteForMango.App).LocationList.Count - 1;

                    // Fetch WX data & fill.
                    this.GetYahooWeatherAsync(Convert.ToInt32((App.Current as WeatherLiteForMango.App).LocationList[locationNumber].WOEID));
                }
                else
                {
                    // Ask user to enter new locations.
                }
            }
        }

So, you see how we take the user directly to a city’s weather if coming from a secondary Live Tile or just load the last visited city’s weather if we have any. Pretty cool right? I can think of so many instances where such direct deep-linking is going to induce rich user experience.

Now, all these secondary Live Tiles will not be of much use, unless they relayed live weather updates from some cloud service. Thankfully, even in Mango, the way we subscribe to Live Tile notifications has not changed much. We use the same techniques to either find a unique MPNS channel or create/save one if needed. Here’s some code to handle channels for Push Notification subscriptions:


        // Try to find existing Channel from Isolated Storage or create new one.
        if (!this.TryFindChannel())
           this.DoConnect();

        private bool TryFindChannel()
        {
            bool gotcha = false;

            // Look for saved Channels in Isolated Storage.
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {                
                if (isf.FileExists(somePredefinedFileName))
                {                    
                    using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(somePredefinedFileName, FileMode.Open, isf))
                    {
                        using (StreamReader sr = new StreamReader(isfs))
                        {
                            string uri = sr.ReadLine();
                            httpChannel = HttpNotificationChannel.Find(somePredefinedChannelName);

                            if (null != httpChannel)
                            {
                                if (httpChannel.ChannelUri.ToString() == uri)
                                {
                                    // If Channel is found, make necessary Subscriptions
                                    this.SubscribeToChannelEvents();
                                    this.SubscribeToNotifications();
                                    this.SubscribeToService();
                                    gotcha = true;
                                }
                                sr.Close();
                            }
                        }
                    }
                }               
            }

            return gotcha;
        }

        private void DoConnect()
        {          
            // First, try to pick up existing channel.
            httpChannel = HttpNotificationChannel.Find(somePredefinedChannelName);

            if (null != httpChannel)
            {
                this.SubscribeToChannelEvents();
                this.SubscribeToService();
                this.SubscribeToNotifications();
            }
            else
            {
                // Create the new channel.
                httpChannel = new HttpNotificationChannel(somePredefinedChannelName, "WeatherLiteBackendService");

                this.SubscribeToChannelEvents();
                httpChannel.Open();
            }           
        }


        private void SaveChannelInfo()
        {
            // Save off Channel Info into Isolated Storage as a file.
            using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
            {            
                using (IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(somePredefinedFileName, FileMode.Create, isf))
                {
                    using (StreamWriter sw = new StreamWriter(isfs))
                    {                        
                        sw.WriteLine(httpChannel.ChannelUri.ToString());
                        sw.Close();                        
                    }
                }
            }
        }

And, if we did end up creating a new MPNS channel, we wait for the “httpChannel_ChannelUriUpdated” event so that we subscribe to a few things with the channel URI. Notice that the same API — BindToShellTile(), that we had used before Mango can now be utilized to subscribe all the secondary Live Tiles to receiving Push Notifications. This now essentially tells the OS — Hey, be ready to receive any Tile updates for this app! Here’s some code:


        private void SubscribeToChannelEvents()
        {
            // Register to UriUpdated event - occurs when channel successfully opens.
            httpChannel.ChannelUriUpdated += new EventHandler(httpChannel_ChannelUriUpdated);
        }   

        void httpChannel_ChannelUriUpdated(object sender, NotificationChannelUriEventArgs e)
        {
            // Once the client has successfully created a channel with MPNS, save & subscribe.
            Dispatcher.BeginInvoke(() => this.SaveChannelInfo());           
            this.SubscribeToService();
            this.SubscribeToNotifications();
        }     

        private void SubscribeToNotifications()
        {
            // Bind to Tile Notificaions.            
            try
            {
                if (httpChannel.IsShellTileBound != true)
                {
                    httpChannel.BindToShellTile();
                }
            }
            catch (Exception)
            {
                // Do stuff.
            }
        }

Now, I need to mention something we shall cover in the next post — our cloud service backend running in Azure. For now, let us just assume that we have a service running somewhere which is willing to accept requests from our app & push out live weather updates to our secondary Live Tiles. The cloud service is referenced within our phone client project so that we get the proxy made & can make method calls against this service. Here’s the simple set-up:

Weather Project

Weather Project

 

 

 

 

 

 

 

 

 

 

Having access to our cloud service’s methods, we can now easily let the service know the details it needs to push our live weather updates to MPNS, and eventually to the phone client as Live Tiles. One important thing to remember is that our cloud service needs to know exactly which Live Tile to update for each city! So, in addition the channel URI & the city’s WOEID, we also need to let our service know about the unique URL that identifies the secondary Live Tile we want the service to update.


        private void SubscribeToService()
        {
            // Reference to backend service.
            RegistrationServiceClient client = new RegistrationServiceClient();            
            client.RegisterAsync(httpChannel.ChannelUri.ToString(), this.currentWOEID, this.currentLiveTileURI.ToString());
        }

That’s it! Now, our secondary Live Tile can stay happily pinned to start screen and our cloud service should wake up from time to time (based on how you configure it) and send out Live Tile updates with latest weather. Not too difficult; just a few moving pieces. Hope this was helpful.

Adios!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s