UWP: App Lifecycle and Saving State

  1. App lifecycle
    1. The app lifecycle are the states a UWP app goes through when executed, navigated away from, and terminated
    2. Figure borrowed from article Windows 10 universal Windows platform (UWP) app lifecycle

      App lifecycle
    3. UWP apps have four states:
      1. NotRunning - Has not been launched or was once Suspended and could not be kept in memory
      2. Running in background - Default state that an app is launched, activated, or resumed into or when app is minimized or another app has the focus; UI is not visible
      3. Running in foreground - Splash screen is removed, and UI is visible
      4. Suspended - App in background state for N seconds and not doing anything; app remains in memory unless OS needs more
    4. App events:
      1. Activated - Splash screen is displayed
      2. LeavingBackground - App is leaving background state and about to run in foreground
      3. EnteredBackground - App is entering background state; may save state if app is doing background work
      4. Suspending - App is moving to Suspended state; app should save relevant app and user data in case user or OS closes app
      5. Resuming - User switches to app or device comes out of a low power state
    5. Demo to see events occuring - run in Debug mode and watch debug Output window in VS
      // From App.xaml.cs
      sealed partial class App : Application
      {
      	public App()
      	{
      		this.InitializeComponent();
      		this.Suspending += OnSuspending;
      
      		// Add event handlers 
      		this.EnteredBackground += App_EnteredBackground;
      		this.LeavingBackground += App_LeavingBackground;
      		this.Resuming += App_Resuming;
      	}
      	
      	private void App_Resuming(object sender, object e)
      	{
      		Debug.WriteLine("Resuming");
      	}
      
      	private void App_LeavingBackground(object sender, LeavingBackgroundEventArgs e)
      	{
      		Debug.WriteLine("LeavingBackground");
      	}
      
      	private void App_EnteredBackground(object sender, EnteredBackgroundEventArgs e)
      	{
      		Debug.WriteLine("EnteredBackground");
      	}
      	
      	...
      	
      	private void OnSuspending(object sender, SuspendingEventArgs e)
      	{
      		Debug.WriteLine("Suspending");
      		...
      	}
      }	
      
    6. CRASH! - If app stops responding or generates an exception, Windows asks the user for consent to send a problem report to Microsoft
    7. Apps should restore their UI state so the user's work is not lost
    8. Session data vs. persistent data
      1. Session data - temporary data that is relevant to the user’s current session
        1. Examples
          1. News item user is reading in news app
          2. User's placement of tiles during one move in Scrabble game
        2. Should be restored if user navigates back to app
        3. A session ends when the user closes the app, logs off, or reboots the computer
        4. Usually saved when enabling NavigationCacheMode
      2. Persistent data - data that persists across sessions and must always be accessible to the user
        1. Examples
          1. User's preferred news categories
          2. Score of all ongoing Scrabble games
        2. Should be restored after app is closed and restarted
        3. Could be stored on the local machine (LocalSettings) or in the cloud (RoamingSettings) so the data is accessible to the same user on different machines
  2. Saving state (persistent data)
    1. Saving persistent data with ApplicationData class
      1. LocalFolder for saving persistent data in files on the device
      2. LocalSettings for saving key-value pairs on the device
      3. RoamingFolder for saving persistent data files in the cloud; data is accessible on different devices to same user
      4. RoamingSettings for saving key-value pairs in the cloud
      5. Other: LocalCacheFolder, TemporaryFolder, SharedLocalFolder
    2. OnSuspending() method in App.xaml.cs called when suspending
    3. Save the navigation state
      private void OnSuspending(object sender, SuspendingEventArgs e)
      {
      	var deferral = e.SuspendingOperation.GetDeferral();
      	//TODO: Save application state and stop any background activity
      
      	Frame frame = Window.Current.Content as Frame;
      	ApplicationData.Current.LocalSettings.Values["NavigationState"] = frame.GetNavigationState();
      
      	deferral.Complete();
      }
      
      1. Calling GetNavigationState() causes OnNavigatedFrom() to be called when the app is terminated, which is ideal for saving the page's state
      2. Warning: Calls to Frame.Navigate() must either pass no second parameter or a second parameter that is a primitive type like string, char, int, etc. If the parameter is an object, an exception will be thrown because the object cannot be serialized automatically by GetNavigationState().
    4. Restore navigation state in OnLaunched() so when app returns to same page when brought back to Running state
      protected override void OnLaunched(LaunchActivatedEventArgs e)
      {
      	// Skip...
      	
      	if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
      	{
      		//TODO: Load state from previously suspended application
      		
      		var navigationState = ApplicationData.Current.LocalSettings.Values["NavigationState"] as string;
      		if (navigationState != null)
      		{
      			rootFrame.SetNavigationState(navigationState);
      		}
      	}
      	
      	// Skip...
      }
      
      1. PreviousExecutionState indicates what caused the app to previously terminate: Terminated or ClosedByUser
    5. Save persistent data in LocalSettings when navigating away from page (OnNavigatedFrom() is called when app is suspended)
      protected override void OnNavigatedFrom(NavigationEventArgs e)
      {
      	ApplicationData.Current.LocalSettings.Values["username"] = username;
      }
      
    6. Restore persistent data when navigating to the page in OnNavigatedTo()
      protected override void OnNavigatedTo(NavigationEventArgs e)
      {
      	if (ApplicationData.Current.LocalSettings.Values.ContainsKey("username"))
      	{
      		string username = ApplicationData.Current.LocalSettings.Values["username"] as string;
      	}
      }
      
    7. Multiple values can be stored in an ApplicationDataCompositeValue object
      // In OnNavigatedFrom
      var composite = new ApplicationDataCompositeValue();
      composite["field1"] = textBox1.Text;
      composite["field2"] = textBox2.Text;
      ApplicationData.Current.LocalSettings.Values["pageState"] = composite;
      
      // In OnNavigatedTo
      var composite = ApplicationData.Current.LocalSettings.Values["pageState"] as ApplicationDataCompositeValue;
      textBox1.Text = composite["field1"] as string;
      textBox2.Text = composite["field2"] as string;
      
    8. To clear the state on a new navigation, check e.NavigationMode
      protected override void OnNavigatedTo(NavigationEventArgs e)
      {
      	// Other possible values: Back, Forward, Refresh
      	if (e.NavigationMode == NavigationMode.New)
      	{
      		// Disregard saved state		
      		ApplicationData.Current.LocalSettings.Values.Remove("pageState");
      	}
      	else if (ApplicationData.Current.LocalSettings.Values.ContainsKey("pageState"))
      	{
      		// Restore saved state
      		var composite = ApplicationData.Current.LocalSettings.Values["pageState"] as ApplicationDataCompositeValue;
      		textBox1.Text = composite["field1"] as string;
      		textBox2.Text = composite["field2"] as string;
      	}
      }   				
      
  3. Simulating suspending, terminating, and restoring an app in Visual Studio
    1. Run debugger (F5)
    2. Select an option from the debugger

      VS2015 debugger lifecycle events
    3. Select Suspend and shutdown so the Suspending event occurs and state management code is executed
    4. Press F5 to see state restored
    5. If you stop the debugger (Debug > Stop Debugging), and restart, LaunchActivatedEventArgs.PreviousExecutionState in OnLaunched() will be ClosedByUser, so SetNavigationState() not called
  4. Serialization
    1. Most apps need to save the state of various objects
    2. Serialization is the process of converting an object into some other representation (text or binary) that can be used to later restore the object
    3. Deserialization is the process of restoring a serialized object back to its original form
    4. Json.NET is a popular library for serializing objects with JSON
      1. Install Json.NET using nuget
        PM> Install-Package Newtonsoft.Json
        
      2. Serialize/deserialize example
        Student stu = new Student();
        stu.Name = "Bob";
        stu.Gpa = 3.5;
        
        // Serialize object to {"Name":"Bob","Gpa":3.5}
        string json = JsonConvert.SerializeObject(stu);
        
        // Deserialize
        Student newStu = JsonConvert.DeserializeObject<Student>(json);
        
      3. Only public properties are read/written, so must add properties to properly serialize private data
      4. Store the serialized object in OnNavigatedFrom and deserialize in OnNavigatedTo