Treasure Map under the bonnet (hood) #7 … Keeping Track

This is probably the last under the bonnet adventure, unless you want more. After looking at the News feature, we will explore of application lifestyle (or lack thereof), the tracking and roaming features.

Application lifecycle

Windows 8 adopted the application lifecycle model which worries about battery life, energy consumption and heat generation, suspending applications when not needed.


For details MSDN is a great resource … start here –>

Windows 8 apps can optionally declare contracts in the application manifest. The ALM Readiness Treasure Map registers the Search provider, which shows results from a search.


When we visualise and peek into the implementation of App class, we note that the treasure map app honours two events, namely OnLaunched and OnSearchActivated. Suspend and terminate (exit) events are not implemented in the App class, which seems to indicate that we do not have to save the state of the application. … good question for our dev lead.image

The associated code for the two events is:

   1:          /// <summary>
   2:          /// Invoked when the application is launched normally by the end user.  Other entry points
   3:          /// will be used when the application is launched to open a specific file, to display
   4:          /// search results, and so forth.
   5:          /// </summary>
   6:          /// <param name="args">Details about the launch request and process.</param>
   7:          protected override void OnLaunched(LaunchActivatedEventArgs args)
   8:          {
   9:              if (args == null)
  10:              {
  11:                  throw new ArgumentNullException("args");
  12:              }
  14:              if (string.IsNullOrWhiteSpace(args.Arguments))
  15:              {
  16:                  this.Launch(typeof(TreasureMapPage));
  17:              }
  18:              else
  19:              {
  20:                  var tokenPosition = args.Arguments.IndexOf('-');
  21:                  var section = args.Arguments.Substring(0, tokenPosition);
  22:                  var id = Convert.ToInt32(args.Arguments.Substring(tokenPosition + 1), CultureInfo.InvariantCulture);
  24:                  switch (section)
  25:                  {
  26:                      case "category":
  27:                          {
  28:                              this.Launch(typeof(Views.Category), id);
  29:                              break;
  30:                          }
  32:                      case "project":
  33:                          {
  34:                              var project = (from c in Database.Categories
  35:                                             from p in c.Projects
  36:                                             where p.Id == id
  37:                                             select p).Single();
  38:                              var param = Tuple.Create(project.Category, project);
  39:                              this.Launch(typeof(Views.Projects), param);
  40:                              break;
  41:                          }
  42:                  }
  43:              }
  44:          }
  46:          /// <summary>
  47:          /// This function is called when the search charm is activated
  48:          /// </summary>
  49:          /// <param name="args">Search Args Passed</param>
  50:          protected override void OnSearchActivated(SearchActivatedEventArgs args)
  51:          {
  52:              if (args == null)
  53:              {
  54:                  throw new ArgumentNullException("args");
  55:              }
  57:              if (Navigation.RootFrame != null)
  58:              {
  59:                  var searchPage = Navigation.RootFrame.Content as Search;
  60:                  if (searchPage != null)
  61:                  {
  62:                      var searchModel = searchPage.DataContext as ViewModels.Search;
  63:                      searchModel.SearchContent(args.QueryText);
  64:                      return;
  65:                  }
  66:              }
  68:              this.Launch(typeof(Search), args.QueryText);
  69:          }

Dev Lead question time …


Q: Would there be any value to implement the other event, i.e. suspend or exit? … why?


Robert MacLean, our dev lead, replies …

We track state as we go along, so there isn't much we need to do on suspend/exit. Potentially we could do some state logic, like what page you are on so if the app exits (from memory constraint) then when you went back in you would be at the same page.


Treasure map tracking

The tracking feature intends to help you keep track of your explorations and which categories and associated gems you have had a look at. It is important to highlight that v2 tells you what you have looked at, not what you have perused from start to finish.

image … showing progress on the Live Tile.

image … showing (1) in-progress and (2) completed treasure hunt progress in the application.

The code that is responsible for the tracking is hiding within the StatusManager.cs class, as highlighted below.


As always the CodeMap feature helps us visualize who calls this code. The LoadDBAsync calls the Load to initialize the running state, the Live Tile code we covered in recent posts calls to determine how much has been completed, and the Execute logic sets the progress and forces a Save. We recommend that you peruse the sample code, when published, to “explore” this class which performs a few interesting calculations.


Dev Lead question time …


Q: Why is the tile showing 0% for planning, when the treasure chests are lighting up in the planning category as well? Could it be that <1% shown as 0%, a la integer?


Robert MacLean, our dev lead, replies …

Definitely, there is rounding so even 0.0001% then would show as 0%!


Roaming the settings

The really interesting feature and code ensures that any update in terms of the treasure progress is saved to the user’s roaming application data. This means that you can use the treasure map on a number of devices and always get the latest progress state to follow you around via the roaming profile. As shown below, the Execute method calls the SetStatus method, which in turn forces a Save.


The interesting code is the JSon serializer and the Windows.Storage.ApplicationData.Current.RoamingSettings.

   1:  /// <summary>
   2:          /// Loads the list of guidance status from roaming storage.
   3:          /// </summary>
   4:          private void Load()
   5:          {
   6:              if (Windows.Storage.ApplicationData.Current.RoamingSettings.Values.ContainsKey("SavedStatus"))
   7:              {
   8:                  // Deserialize the JSON text
   9:                  var jsonText = Windows.Storage.ApplicationData.Current.RoamingSettings.Values["SavedStatus"].ToString();
  10:                  using (var memStream = new MemoryStream(System.Text.UnicodeEncoding.Unicode.GetBytes(jsonText)))
  11:                  {
  12:                      var jsonSer = new DataContractJsonSerializer(typeof(List<GuidanceStatus>));
  14:                      memStream.Position = 0;
  16:                      this.savedStatus = (List<GuidanceStatus>)jsonSer.ReadObject(memStream);
  17:                  }
  18:              }
  19:              else
  20:              {
  21:                  this.savedStatus = new List<GuidanceStatus>();
  22:              }
  24:              // Update the Category/Project/Guidance object hierarchy with the appropriate status values
  25:              // for each of the persisted Guidance items
  26:              foreach (var item in this.savedStatus)
  27:              {
  28:                  this.UpdateGuidanceStatus(item.Id, item.Status);
  29:              }
  31:              this.UpdateStatistics();
  32:          }
  34:          /// <summary>
  35:          /// Saves the list of guidance statuses to roaming storage.
  36:          /// </summary>
  37:          private void Save()
  38:          {
  39:              // Serialize the status list as JSON text
  40:              var memStream = new MemoryStream(); // this doesn't need a using as the reader will dispose of it
  42:              var jsonSer = new DataContractJsonSerializer(typeof(List<GuidanceStatus>));
  44:              jsonSer.WriteObject(memStream, this.savedStatus);
  45:              memStream.Position = 0;
  47:              using (var reader = new StreamReader(memStream))
  48:              {
  49:                  Windows.Storage.ApplicationData.Current.RoamingSettings.Values["SavedStatus"] = reader.ReadToEnd();
  50:              }
  51:          }

Dev Lead question time …


Q: What, if any, are the limitations of roaming storage?


Robert MacLean, our dev lead, replies …

For the Roaming Settings we roughly have 64K of total storage, however each individual setting cannot be more than 8K.

For the roaming folder we roughly have 100K of space.

I say roughly because there isn't exactly a hard limit & the limits can be influenced by many factors. There is an API to check the quota that is available.


Q: Willy logs onto his Surface in Italy and his Laptop in Vancouver. On both machines he explores the treasure map, but using different routes. He then closes the application on both devices … which state wins?


Robert MacLean, our dev lead, replies …

Which ever was used last.



What would you like us to cover next? Candid feedback appreciated Smile

Last Dev Lead question for the time being …


Q: Why did you accept the dev lead role on this treasure hunt and why are you tolerating the distracting questions on this blog?


Robert MacLean, our dev lead, replies …

I accepted the dev lead role because I wanted to do more for the Rangers and the ALM community. My time is so tight that doing multiple Ranger projects isn't a feasible idea (I have tried and failed in the past to this regard) so focusing on one means I can contribute & contribute well to the project. Why do I tolerate the questions? Two reasons, one is again the ability to do more for the ALM community by sharing. Secondly it makes me think and have to research and learn. So I get benefit out of it too!



Skip to main content