EventBus in a Windows 8 app

eventBusWinAppHow to communicate different pages of a Windows 8 App? How to manage the life cycle of the pages?

Pages are instantiated by the framework when asked to navigate:

*.xaml.cs:

  1. frame.Navigate(typeof(MyPage));

It will be a new instance of MyPage everytime unless the pages are cached. To set up the page cache, add this line to the beginning of

MyPage.xaml:

  1. <Page
  2. x:Name="pageRoot"
  3. x:Class="MyNamespace.MyPage"
  4. NavigationCacheMode="Enabled"
  5. ...
  6.  

 
Cache can be clear as described here as long as the NavigationCacheMode is "Enabled" and not "Required". The "Required" pages can't be deleted from the cache as explained by Andreas Hammar.

The page's life cycle is controlled by the framework although we can configure the cache, therefore my objects - those whose life cycle is controlled by myself - should not reference pages in order to avoid memory leaks and undesired side effects.

We've decided that Pages and other UserControls are the ones which create and manage ViewModel's life cycle (we're using MVVM pattern). The page's codebehind (MyPage.xaml.cs) contains a reference to ViewModel.cs which is a plain old C# object that contains the GUI logic. MyPage instantiates the ViewModel in its constructor. So there should be no references to ViewModels from other objects.

MyPage.xaml.cs

  1. public MyPage(){
  2. InitializeCompontent();
  3. ViewModel = new ViewModel();
  4. DataContext = ViewModel;
  5. }

However I can't avoid references to the ViewModel from the EventBus (simple pubsub static class) because it's the mechanism we are using to communicate different parts of the application:

ViewModel.cs:

  1. public void SubscribeToEventBus(){
  2. EventBus.Subscribe(this); // static method
  3. }
  4. public void UnsubscribeEventBus(){
  5. EventBus.Unsubscribe(this); // static method
  6. }

To work around this problem, I make sure the ViewModel contains references to the bus only when it's active (visible on screen):

MyPage.xaml.cs:

  1. public override void OnNavigatedTo(){
  2. ViewModel.SubscribeToEventBus();
  3. ViewModel.Initialize(); // optional
  4. }
  5. public override void OnNavigatedFrom(){
  6. ViewModel.UnsubscribeEventBus();
  7. }

When the ViewModel requires some data previously generated in another view and stored in the AppState, it raises an event through the bus saying it's loaded and then the AppState listener replies back through the bus sending the required data.

We haven't found a built-in way to reset the application clearing all data so we've implemented a reset method. The reset is an event triggered by a button in the bottom bar, sent through the bus. Our App.xaml.cs object which is the singleton that starts the application handles the event clearing all data. The sequence is important to avoid memory leaks

App.xaml.cs:

  1. public void Handle(AppReset appResetArgs){
  2. EventBus.Clear();
  3. AppState.Reset();
  4. InitializeServices();
  5. EventBus.Subscribe(this);
  6. ResetXamlCache(); // this must be the last thing to clear
  7. NavigateToRootPage();
  8. }

Only the App object subscribes to the AppReset event. Other global events like a connection failure are also handled by the App object.

A final reflection is,... Why do we need an EventBus at all? We could just inject the same AppState instance in every ViewModel to share data among the different screens. Something like a NavigationService could have solved this for us. The following pseudocode illustrates the idea:

NavigationService.cs:

  1. public static void NavigateTo<T>(){
  2. var frame = (Frame) Window.Current.Content;
  3. var viewModel = Factory.ViewModelFor<T>();
  4. frame.Navigate(typeof (T), viewModel);
  5. }

MyPage.xaml.cs

  1. protected override void OnNavigatedTo(NavigationEventArgs e){
  2. ViewModel = e.Parameter as ViewModel;
  3. }

Then in the Factory we could inject the same AppState to all ViewModels and manage their life cycle. This indirection level could have changed out architecture. A custom base paged could implement the "OnNavigatedTo" to avoid duplication.

Thanks to my friend Juan M. Gomez for the idea of the navigation service.

Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.