In my post Simplyfying the Windows Phone Development Experience! Codename: LionHeart I explain that LionHeart is a demo app that I’m using to prove out a few lessons we’ve learned here at IK from our last Windows Phone project. In this post I will start to get into the meat of the app starting in a class named CoreApplicationService (CAS).
What is CoreApplicationService’s Purpose?
Event Forwarding
One of the purposes of the CoreApplicationService (CAS) is to expose events from across the app to ViewModels (VMs) or any other class that is not instantiated by App.xaml. This class acts, in a very simplified way, as an event aggregator. It is not, however, an event aggregator as found in Prism. Instead it’s more of a one to one event forwarding class. For example, in App.xaml there are two events named Deactivated and Closing. Therefore, in the CAS there are two identical events named Deactivated and Closing. When the event is handled in App.xaml.cs we simply forward the handling onto the CAS as show in this example:
public partial class App : Application { private void ApplicationDeactivated(object sender, DeactivatedEventArgs e) { CoreApplicationService.Instance.OnDeactivated(e); } }
Currently the events handled in the CAS are:
From App.xaml
- Deactivated
- Closing
- Obscured
- Unobscured
- NavigationFailed
- UnhandledException
From PhoneApplicationFrame
- Navigating
- Navigated
In all cases, the CAS acts as a forwarding system for any object the desires to subscribe to these events. In LionHeart those objects are usually VMs. This pattern allows the VM to be responsible for knowing when and what to do during these events. This pattern has made more and more sense as we’ve discussed possible solutions to our problem of telling VMs when to Deactivate (prepare for tombstoning) etc.
Error Reporting
Another responsibility of CAS is to prompt the consumer of the app to send error data to the developer. We found Little Watson created by Andy Pennell at Microsoft which simply collects unhandled exception information and stores it in a file in IsolatedStorage before the app closes. The next time the consumer launches the app they are prompted to send the error file to the developer, which they can choose to not send it if they desire. The error file is then deleted and the application will not prompt again until an error file exists again. This is so helpful I cannot even begin to express how many bugs we have been able to track down because of this tool. I want to start buffing this helper class out with logging since the error information we receive on the phone is not as helpful as it could be. Adding a logging feature that began each time the application was launched would be of even greater benefit.
Navigation
This purpose is what originally drove us to create the CAS. In fact we had originally named the CAS as NavigationService, but as it’s functionality increased and purpose morphed we decided that name was to specific and did not convey what we wanted. Navigation will be covered in greater detail in another post. In simple terms we use a dictionary of object to string mappings. Each string represents the Uri to a page. These mappings are set immediately after initializing the CAS as shown in the section below “How to use CoreApplicationService.”
public partial class App : Application { private void InitializeNavigationMappings() { CoreApplicationService.Instance.Mappings.Add(ViewKeys.HOME_VIEW_KEY, "/Views/HomeView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.MY_VIEW_KEY, "/Views/MyView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.ALL_CLIENTS_VIEW_KEY, "/Views/AllClientsView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.CLIENT_VIEW_KEY, "/Views/ClientView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.MY_REPORTS_VIEW_KEY, "/Views/MyReportsView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.REPORT_VIEW_KEY, "/Views/ReportView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.ALL_SESSIONS_VIEW_KEY, "/Views/AllSessionsView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.MY_SESSIONS_VIEW_KEY, "/Views/MySessionsView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.SESSION_VIEW_KEY, "/Views/SessionView.xaml"); CoreApplicationService.Instance.Mappings.Add(ViewKeys.SETTINGS_VIEW_KEY, "/Views/SettingsView.xaml"); } }
In the example code above you will notice the use of ViewKeys which is a struct that contains string values that we use as keys. The key does not need to be a string instead it can be any object. Sometimes we will use typeof(object) for views that represent specific types. We found that strings were required for some views and decided to go with all strings for consistency. Remembering that VMs will be requesting navigation most of the time we did not use typeof(Page) as the key, because VMs should not have access to those types.
How to use CoreApplicationService
The CAS is provided via a static property of itself named Instance as a singleton. There are two times the CAS will need to be instantiated. The first is when the application is Launching. The second case is when the application is being Activated, but only if there is no application state. In both cases no VMs exist yet, therefore the CAS will not forward the Launching and Activating events because no object would or could receive them. Instead we use the events to initialize the CAS.
public class CoreApplicationService { private static CoreApplicationService _instance; public static CoreApplicationService Instance { get { return _instance ?? (_instance = new CoreApplicationService()); } } } public partial class App : Application { private void Initialize(bool isLaunching) { CoreApplicationService.Instance.Initialize(RootFrame, isLaunching); InitializeNavigationMappings(); //As shown in above section “What is CoreApplicationService’s Purpose?” and subsection “Navigation”. } private void ApplicationLaunching(object sender, LaunchingEventArgs e) { Initialize(true); } private void ApplicationActivated(object sender, ActivatedEventArgs e) { if (!e.IsApplicationInstancePreserved) { Initialize(false); } } }
Initialize accepts two parameters: the PhoneApplicationFrame which is used for navigation, and a boolean indicating if the application is launching.
public class CoreApplicationService { public void Initialize(PhoneApplicationFrame frame, bool isLaunching) { LittleWatson.CheckForPreviousException(); _frame = frame; _frame.Navigated += FrameNavigatedHandler; _frame.Navigating += FrameNavigatingHandler; PersistTombstoningValue(IS_LAUNCHING_KEY, isLaunching); } }
The reason for the boolean is for VM initialization. This will be explained in much greater detail later on, but for those curious minds out there during Activating (without state) when VMs initialize there are no navigation parameters. This is used as an indication that the application was tombstoned. Because this same state exists during Launching we must differentiate the two somehow. We got around this by adding a boolean to the tombstoning values that is checked for when no navigation parameters exist and indicates the application is Launching not Activating. As I stated before I will cover this in later posts in greater detail.
Conclusion
This concludes the introduction into the CoreApplicationService and how it plays into the lifecycle of the phone app. This service is the backbone to LionHeart and all phone apps that use it. As we cover navigation and how VMs integrate with this service you’ll understand why it is so important and why it is so helpful. As always if you have any suggestions for improvement, comments, or questions please post them. This is not meant to be a one man show, but rather a starting point for phone app architecture.
Pingback: Simplifying the Windows Phone development experience! Codename: LionHeart | InterKnowlogy Blogs
Pingback: WP7 Simplified: CoreApplicationService (Navigation) | InterKnowlogy Blogs