Saving user details on OnSuspending event for Metro Style Apps

I recently started getting to know about Metro Style Apps on Windows 8. It looks pretty interesting so far and VS2011 definitely helps making it easier to learn and create Metro Style Apps. One of the features available for developers is the ability to save user data so it can be retrieved the next time the app is run after being closed by the user or even launched from back suspended state.

Here’s a little history on this whole ‘suspended’ state of a Metro Style app:

Once the user say, ‘alt+tab’s to another application or even closes it, Win8 takes the application through a ‘suspended’ phase which is where a developer has the opportunity to save application state or even user data. There’s a 5-sec window that a developer gets to do all the things needed to save the data. Bear in mind that if your activity ends up taking more than 5 seconds, it will be ‘mercilessly’ terminated without any notification… pretty rough don’t you think?

Let’s see how to wire up this successfully.

I’ve created a C# Metro Style App with a blank xaml page:

   1:  <Canvas>
   2:          <TextBox Canvas.Left="115" TextWrapping="Wrap" Canvas.Top="65" x:Name="IdTextBox"
   3:                  KeyUp="IdTextBox_KeyUp"/>
   4:          <TextBlock Canvas.Left="90" TextWrapping="Wrap" Text="Id" Canvas.Top="75"/>
   5:          <TextBox Canvas.Left="115" TextWrapping="Wrap" Canvas.Top="120"x:Name="NameTextBox"
   6:                  KeyUp="NameTextBox_KeyUp"/>
   7:          <TextBlock Canvas.Left="70" TextWrapping="Wrap" Text="Name" Canvas.Top="129"/>
   8:  </Canvas>

I now need a ‘bucket’ to put the values of these controls into and then save the entire bucket. I’m going to create a Statistics class.

   1:  public class Statistics
   2:  {
   3:      public int Id { get; set; }
   4:      public string Name { get; set; }
   5:      public bool HasValue { get; set; }
   6:  }

I’m using the two KeyUp events to keep the instance of Statistics updated.

   1:  private void NameTextBox_KeyUp(object sender, KeyEventArgs e)
   2:  {
   3:      App.Stats.HasValue = NameTextBox.Text.Length > 0 || IdTextBox.Text.Length > 0;
   4:      App.Stats.Name = NameTextBox.Text;
   5:  }
   6:   
   7:  private void IdTextBox_KeyUp(object sender, KeyEventArgs e)
   8:  {
   9:      App.Stats.HasValue = NameTextBox.Text.Length > 0 || IdTextBox.Text.Length > 0;
  10:      // I know the below is dangerous, but as Jon Skeet said:
  11:      // "Didactic code is not production code."
  12:      App.Stats.Id = int.Parse(IdTextBox.Text);
  13:  }

I have two fields declared on the App class:

   1:  static public Statistics Stats = new Statistics();
   2:  private const string filename = "HandlingOnSuspendingEventState.xml";
 

The OnSuspending event can be handled in the App.cs class.

   1:  async void OnSuspending(object sender, SuspendingEventArgs e)
   2:  {
   3:      //TODO: Save application state and stop any background activity
   4:      SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();
   5:      await SaveAsync();
   6:      deferral.Complete();
   7:  }

You might have heard this that pretty much everything in Metro Style apps is asynchronous, so you’ll see the async and await keywords quite a bit in these apps. If you like to know more about these keywords, try here and here.

So the Complete event on the SuspendingDeferral notifies to the OS that the app has saved its data and is ready to be suspended. The SaveAsync is where I’m serializing the data I need saved into an xml file.

   1:  async static public Task SaveAsync()
   2:  {
   3:      // Get the output stream for the SessionState file.
   4:      StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsyn(filename,
   5:                         CreationCollisionOption.ReplaceExisting);
   6:      IRandomAccessStream raStream = await file.OpenAsync(FileAccessMode.ReadWrite);
   7:      using (IOutputStream outStream = raStream.GetOutputStreamAt(0))
   8:      {
   9:          // Serialize the Session State.
  10:          DataContractSerializer serializer = new DataContractSerializer(typeof(Statistics));
  11:          serializer.WriteObject(outStream.AsStreamForWrite(), Stats);
  12:          await outStream.FlushAsync();
  13:      }
  14:  }

I will show you later where the file gets saved.

Now upon return, when the app hits the OnLaunched event, we can read the file, deserialize it’s contents and then set the instance of Statistics with the values that were saved earlier.

   1:  async protected override void OnLaunched(LaunchActivatedEventArgs args)
   2:  {
   3:      if (args.PreviousExecutionState == ApplicationExecutionState.Terminated 
   4:          || args.PreviousExecutionState == ApplicationExecutionState.Suspended
   5:          || args.PreviousExecutionState == ApplicationExecutionState.ClosedByUser)
   6:      {
   7:          //TODO: Load state from previously suspended application
   8:          await RestoreAsync();
   9:      }
  10:   
  11:      // Create a Frame to act navigation context and navigate to the first page
  12:      var rootFrame = new Frame();
  13:      rootFrame.Navigate(typeof(BlankPage));
  14:   
  15:      // Place the frame in the current Window and ensure that it is active
  16:      Window.Current.Content = rootFrame;
  17:      Window.Current.Activate();
  18:  }

The thing to take note of is the ‘if’ block. I have expanded the scope of the data restore to the ClosedByUser state as well. What this means is that data restore will be attempted if the app was terminated or suspended by the operating system or even if it was closed by the user.

   1:  static async public Task RestoreAsync()
   2:  {
   3:      // Get the input stream for the SessionState file.
   4:      try
   5:      {
   6:          StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(filename);
   7:          if (file == null) return;
   8:          IInputStream inStream = await file.OpenSequentialReadAsync();
   9:   
  10:          // Deserialize the Session State.
  11:          DataContractSerializer serializer = new DataContractSerializer(typeof(Statistics));
  12:          Stats = (Statistics)serializer.ReadObject(inStream.AsStreamForRead());
  13:      }
  14:      catch (Exception)
  15:      {
  16:          // Restoring state is best-effort.  If it fails, the app will just come up with a new session.
  17:      }
  18:  }

Fine, now that we have the Statistics read back from the data store, we use the OnNavigatedTo event on my BlankPage to set these values back to the UI controls.

   1:  protected override void OnNavigatedTo(NavigationEventArgs e)
   2:  {
   3:      Statistics stats = App.Stats;
   4:      if (stats.HasValue)
   5:      {
   6:          IdTextBox.Text = stats.Id.ToString();
   7:          NameTextBox.Text = stats.Name;
   8:      }
   9:  }

There’s just one last thing that needs to be done. In order for the application to create a file, as a developer you need to provide the capability to do the same. Open up the Package.appmanifest file. In the Capabilities tab and check the Documents Library from the list.

image

See this MSDN article for details on capabilities.

This is what my app looks like before I close it myself.

image

The file itself is saved in the below location:

image

The guid you see there is actually the Package Family Name that can be found in the Package.appmanifest file under the Packaging tab.

image

The data got saved as below:

   1:  <Statistics xmlns="http://schemas.datacontract.org/2004/07/HandlingOnSuspendingEvent" 
   2:              xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
   3:    <HasValue>true</HasValue>
   4:    <Id>31</Id>
   5:    <Name>Arun</Name>
   6:  </Statistics>

There are a couple of caveats however that you should be aware.

  • As mentioned above, there’s a 5 second window wherein you can do all it is you want to do. But this limit is applicable even in the debug mode, in sorts. So while testing this feature, say you close the app and you have put a breakpoint in the OnSuspending event, you’ll lose the control in 5 seconds time. This makes it really difficult for users to test the feature. I have raised a flag for the same on MS Connect site.
  • The second thing is that the user might have to wait for at least 5 seconds before launching the app again. This is to give the time for the entire ‘save’ to happen through the OnSuspending event.

Update: The reply from MS for the <a href="https://connect.microsoft.com/VisualStudio/feedback/details/733795/onsuspending-event-gives-user-only-5-seconds-even-in-debug-mode">flag</a> that I raised is as below:

"When pressing Alt+F4 or dragging the app to the bottom of the screen, suspend event will indeed be followed by terminate event in 5 seconds - even when debugging. To debug your suspend event handler, you would need to trigger the suspend event through Visual Studio using the "suspend" button in Debug Location toolbar. To make sure the toolbar is visible, go to View -> Toolbars menu and check Debug Location."

References:

  • Great link to create metro style apps
  • Guidelines for app suspend and resume

Have a look at the sample application here.

1 Comment

Comments have been disabled for this content.