During the running state of your application, when you use a chooser / launcher or you show the running tasks (holding the back hw button) or you press the start hw button and possibly launch an other application, your application goes from the running state to the deactivated one and then to the dormient one and possibly to the tombstoned one.
Even thought now, with mango’s new dormient state, very often the data end pages of your application are preserved in memory, this is not garanteed, so in your code you have to handle the possibility of a tombstoning and to save (during the deactivanting state) all the relevant data for all your running active pages (and not only application data but also UI ones like the scroll position and the current selected pivot item, for example). The best way to save transient data is using the PhoneApplicationService.Current.State because the access time to the Isolated Storage is slower (and you need to use it mainly when you have to save data for all the life of the installed application, … for example configuration data or application data that could be naw be saved also in a SQL)
It is possible to save the transient state of each page in the OnNavigateTo method and recover it in the OnNavigateFrom one, but a better way is to insert the proper code inside the App.xaml.cs file, in the Application_Deactivated and Application_Activated methods.
For example:
private void Application_Deactivated(object sender, DeactivatedEventArgs e) { TransientState.Set("LoginDataSource", DataSourceManager.LoginDataSource); TransientState.Set("LogsPivotDataSource", DataSourceManager.LogsPivotDataSource); TransientState.Set("PanoramaPageDataSource", DataSourceManager.PanoramaPageDataSource); TransientState.Set("SettingsPivotDataSource", DataSourceManager.SettingsPivotDataSource); }
private void Application_Activated(object sender, ActivatedEventArgs e) { if (!e.IsApplicationInstancePreserved) { // We are coming back from a tombstone - need to restore from the state object LoginDataSource loginSource = TransientState.Get<LoginDataSource>("LoginDataSource"); if (loginSource != null) { DataSourceManager.LoginDataSource = loginSource; } LogsPivotDataSource logsPivotSource = TransientState.Get<LogsPivotDataSource>("LogsPivotDataSource"); if (logsPivotSource != null) { DataSourceManager.LogsPivotDataSource = logsPivotSource; } PanoramaPageDataSource panoramaPageDataSource = TransientState.Get<PanoramaPageDataSource>("PanoramaPageDataSource"); if (panoramaPageDataSource != null) { DataSourceManager.PanoramaPageDataSource = panoramaPageDataSource; } SettingsPivotDataSource settingsPivotDataSource = TransientState.Get<SettingsPivotDataSource>("SettingsPivotDataSource"); if (settingsPivotDataSource != null) { DataSourceManager.SettingsPivotDataSource = settingsPivotDataSource; } }
Where I make use of the following utility static class to set/get into the PhoneApplicationService.Current.State using generics:
/// <summary> /// Wraps calls to the Microsoft.Phone.Shell.PhoneApplicationService helper class, which handles the phone’s transient state, with generic get/set methods. /// Lo State storage è molto veloce perchè è in memoria mentre nell'IsolatedStorage si ha un accesso più lento essendo in RAM non volatile (persistente finchè l'app è installata) /// </summary> public static class TransientState { public static void Set<T>(string key, T state) { PhoneApplicationService.Current.State[key] = state; } public static T Get<T>(string key, bool remove = true, T defaultValue = default(T)) { object state; if (!PhoneApplicationService.Current.State.TryGetValue(key, out state)) { state = defaultValue; } else { if (remove) { PhoneApplicationService.Current.State.Remove(key); } } return (T)state; } }
Note that in the method Application_Activated the transient data are restored only if the stombstone happened, that is only if (!e.IsApplicationInstancePreserved). If you put a break point while debugging, you’ll see that very often in Mango your application remain in the dormient state. If you want to force the tombstoning for debugging reasons, it is better you open your project properties panel and click the debug tab checking Tombstone upon deactivation while debugging!
For example, the LoginDataSource could be the following:
public class LoginDataSource : BaseEntity { private User _user; public LoginDataSource() { User = new User(); } public User User { get { return _user; } set { _user = value; OnPropertyChanged("User"); } } public void Reset() { _user.Username = ""; _user.Password = ""; _user.Authenticated = false; _user.Group = UserGroup.NONE; } } [DataContract] public class BaseEntity : INotifyPropertyChanged{ public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string property){ try{ if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property)); } catch (Exception ex){ //string s = ex.Message; } } }
BE CAREFUL that all the object in each DataSource class must be serializable. You can also choose not to persist some properties and in that case you need to specify the appropriate attribute to tha class ( [DataContract] ) and to each property you want to serialize ( [DataMember] ) and nothing for the others you won’t to save. For example, in the following datasource we won’t to serialize some properties like LevelInfoLogList :
[DataContract] public class LogsPivotDataSource : BaseEntity { public LogsPivotDataSource() { LogList = new LogList(); } #region serialized private long _workRequestId; [DataMember] public long WorkRequestId { get { return _workRequestId; } set { _workRequestId = value; OnPropertyChanged("WorkRequestId"); } } private LogList _logList; [DataMember] public LogList LogList { //for PivotItem Name="All" get { return _logList; } set { _logList = value; OnPropertyChanged("LogList"); OnPropertyChanged("LevelErrorLogList"); OnPropertyChanged("LevelWarningLogList"); OnPropertyChanged("LevelInfoLogList"); OnPropertyChanged("ForWrLogList"); } } private int _logPagePivotSelectedIndex; [DataMember] public int LogPagePivotSelectedIndex { get { return this._logPagePivotSelectedIndex; } set { this._logPagePivotSelectedIndex = value; base.OnPropertyChanged("LogPagePivotSelectedIndex"); } } private double _logPagePivotScrollVerticalOffset; [DataMember] public double LogPagePivotScrollVerticalOffset { get { return _logPagePivotScrollVerticalOffset; } set { this._logPagePivotScrollVerticalOffset = value; base.OnPropertyChanged("LogPagePivotScrollVerticalOffset"); } } #endregion serialized #region NOT serialized /// <summary> /// Filter only the Info logs /// </summary> public LogList LevelInfoLogList { //for PivotItem Name="LevelInfo" get { if (_logList == null) { return null; } //among all logs in DataSourceManager.LogsPivotDataSource.LogList, I filter only thats that have LogLevel equal to INFO IEnumerable<Log> uiFilteredItems = from r in _logList where isValid(r, null, LogLevel.INFO, null, null, null) select r; return new LogList(uiFilteredItems); } } .... #endregion NOT serialized #region private /// <summary> /// Return true if all the not null verified /// Log is of the category logCategory, level logLevel and of date betwwen fromDate and toDate /// </summary> /// <param name="log"></param> /// <param name="logCategory"></param> /// <param name="logLevel"></param> /// <param name="fromDate"></param> /// <param name="toDate"></param> /// <returns></returns> private bool isValid(Log log, LogCategory? logCategory, LogLevel? logLevel, DateTime? fromDate, DateTime? toDate, long? workRequestId) { if ((logLevel != null) && (log.Level != logLevel)) { return false; } if ((logCategory != null) && (log.Category != logCategory)) { return false; } if ((fromDate != null) && (log.Date < fromDate)) { return false; } if ((toDate != null) && (log.Date > toDate)) { return false; } if ((workRequestId != null) && (log.WorkRequestId != workRequestId)) { return false; } return true; } #endregion private }