WP7 Contrib – When messaging becomes messy and services shine

In a previous post I introduced you to the Last Message Replay Messenger, in this post I wanted to dig into something that happened recently where I needed to provided the ability in the application for the user to add an item to a favourites list. Ok so fairly straight forwards, however there was a slight twist. Not only can the user add items to their favourites from a list but they can also add an item to their favourites list from a number of different places in the application. Each view has a different VM and we don’t want to chain the VM’s together as then they know about each other; we also don’t want to use the VM Locator from the Xaml and call the same relay command to perform the action; so on the surface this looks like a job for a messenger :-

  1. Define the Message which wraps up the data
  2. Add the IMessenger interface as a parameter in the VM constructor
  3. Register the IMessenger interface with the Last Message Replay Messenger
  4. In the originator register the type of message data your interested in with the Notification Message Action
  5. In the VM which is interested in the message register for the Message defined in step 1
  6. Provide a method that deals with the received messages and sets up the data context of the View

Now this works just fine when I want to pass data to disconnected lazy loading VM’s but I got into trouble when it came to stashing this content. The main reason is that it comes down to a level of responsibility which results in the question of who owns the data ? and who is responsible for storing the data ?

What I ended up with was a collection of favourites that were being managed by each VM that required their Views to have the ability; add to the favourites list and then fire a message broadcasting that here is the new favourite item which needs to be added into the favourites collection. Cool!

So all is cool now? Well sure until it comes to building out the persistence mechanism using the override Activate and DeActive methods that the VM Base provides us with in order to store the data. My next move was then to start changing the data inside of the Message to a collection of the same type, but this solution means that I now pass around a reference pointer to the collection in the message and each VM is then responsible for adding new items. The Favourites VM is then inherently responsible for persisting the collection. Ok so that gets us closer. Unfortunately, the Favourites Page and VM are not primaries in the user journey through the app therefore, there is a high potential that the user will not have viewed the Favourites Page resulting the Favourites VM not being instantiated and now things start to get sticky again….

At this point its time for a brew…. I could continue trying to bend the message pattern to fit our needs but its generating a large amount of friction when we try and push. This is where using a service makes far more sense. Now, if you chat to Ollie he will say well this is all about “ask don’t tell, Rich”. Therefore, moving to a more service style pattern similar to the one used in the service layer for our Apps results in those VM’s which need to use the favourites service have this injected during startup via the bootstrapper. Unlike our current situation where each VM has to register for the message. This gets us back into check with a single responsibility pattern. As the favourites service is now responsible for anything to do with favourites cleaning up the implementation code in the VM’s wanting to use the service. Sweet!

Now I really like this pattern as its super simple to implement and by taking advantage of IStorage and IStorageService which can be found in the WP7C not only do we have a simple pattern for sharing data and operating on that data, we also get the ability to stash this content into Isolated Storage.

The best way for me to show this off is to do a sample, so I am going to build on the sample from the previous post, its also in the spikes directory on Codeplex. What we are going to add to this is a favourites page, wired up to a Favourites VM, and a favourites service that has the ability for our user to do an add to favourites from the list (Main VM)and an add to favourites from the details page (Child VM).

First, up we need to create an interface, in my case its called IFavouriteSearch and it going to provide a service to retrieve the current collection of favourites, add a new favourite, query to see if the service already contains a particular favourite and also remove a favourite from the collection.

using ServiceStyleRatherThanMessengerStyle.Model;

using WP7Contrib.Collections;

/// <summary>
/// The favourites service contract.
/// </summary>
public interface IFavouritesService
{
        #region Properties

        /// <summary>
        /// Gets Favourites.
        /// </summary>
        ReadOnlyObservableCollection<Person> Favourites { get; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The add.
        /// </summary>
        /// <param name="person">
        /// The currently selected person.
        /// </param>
        void Add(Person person);

        /// <summary>
        /// The is a favourite.
        /// </summary>
        /// <param name="person">
        /// The person.
        /// </param>
        /// <returns>
        /// The is a favourite.
        /// </returns>
        bool IsAFavourite(Person person);

        /// <summary>
        /// The remove favoutite.
        /// </summary>
        /// <param name="person">
        /// The person.
        /// </param>
        void RemoveFavourite(Person person);

        #endregion
}

That’s the service defined.

Next up we need to define a concrete class called the Favourite Service which implements the interface, the important part to remember here is that we want to stash the collection to isolated storage in order to persist the selected favourites between sessions. In order to do this we can simply pass into the constructor the IStorageService interface, if you wanted to use the logging parts of WP7C then you can added this into the constructor parameter list.

public FavouritesService(IStorageService storageService, ILog log)
{
   this.storageService = storageService;
   this.log = log;
}

So this is the cool part, when we expose the favourites collection all we need to do is work out if we are rehydrating the collection for the first time from Isolated Storage and load up the collection in ISO, otherwise we new up a collection of the required type. Each piece of content that is stored into ISO has its own key, in my case its called favourites and its used as the Unique ID to retrieve the specified content. The code to Load from Storage is boiler plate and done once it’s a rinse and repeat style pattern.

/// <summary>
/// Gets Favourites.
/// </summary>
public ReadOnlyObservableCollection<Person> Favourites
{
   get
   {
      if (!this.loaded)
      {
         this.LoadFromStorage();
      }

       return new ReadOnlyObservableCollection<Person>(this.favourites);
   }
}

The management methods that operate on the collection we build out as normal and then decorate this code with the storage calls. These are rather easy to remember; once the collection has been updated then we make a call to write the list into ISO associated with our Unique ID, then we flush the storage buffer.

/// <summary>
/// Adds a person to the favourites.
/// </summary>
/// <param name="person">
/// The currently selected person.
/// </param>
public void Add(Person person)
{
   if (person == null)
   {
      return;
   }

   if (!this.loaded)
   {
      this.LoadFromStorage();
   }

   if (this.favourites.Contains(person))
   {
      return;
   }

   this.favourites.Add(person);
   this.storage.Write(StorageKey, this.favourites.ToList());
   this.storage.Flush();
}

/// <summary>
/// The is a favourite.
/// </summary>
/// <param name="person">
/// The person.
/// </param>
/// <returns>
/// Returns true if the person is a favourite.
/// </returns>
public bool IsAFavourite(Person person)
{
   if (person == null)
   {
      return false;
   }

   if (!this.loaded)
   {
      this.LoadFromStorage();
   }

   return this.favourites.Contains(person);
}

/// <summary>
/// The remove favoutite.
/// </summary>
/// <param name="person">
/// The persoh.
/// </param>
public void RemoveFavourite(Person person)
{
   if (person == null) 
   {
      return;
   }

   if (!this.loaded)
   {
      this.LoadFromStorage();
   }

   if (!this.favourites.Contains(person))
   {
      return;
   }

   this.favourites.Remove(persoh);
   this.storage.Write(StorageKey, this.favourites);
   this.storage.Flush();
}

Lets now take a look at the Load From Storage method.

/// <summary>
/// The load from storage.
/// </summary>
private void LoadFromStorage()
{
   if (this.loaded)
   {
      return;
   }

   lock (this.sync)
   {
      if (this.loaded)
      {
         return;
      }

      this.favourites = new ObservableCollection<Person>();
      this.storage = this.storageService.OpenPersistent(StorageKey);

      var favs = this.storage.Read<List<Person>>(StorageKey);
      if (favs == null || favs.Count == 0)
      {
         this.log.Write("FavouritesService: No favourites");
      }
      else
      {
         foreach (Person fav in favs)
         {
              this.log.Write("FavouritesService: Loading Person, hash code - " + fav.GetHashCode());
              this.favourites.Add(fav);
         }
     }

      this.loaded = true;
   }
}

When we need to read the collection we use the Storage service to setup the storage mechanism once this has been done happy days simply call the Read method telling what type is being rehydrated using the specified Unique key that was defined earlier. Iterate over what comes out and add it to our collection.

Now we need to be able to use the service so we head over to the bootstrapper and crank out some register and resolve calls for the service.

this.Container.Register<ILogManager>(c => new LoggingService("LastReplayMessenger"));

this.Container.Register<ILog>(c => this.Container.Resolve<ILogManager>());

var assemblies = new List<Assembly> { this.GetType().Assembly, typeof(BaseModel).Assembly };

this.Container.Register<IStorageService>(c => new StorageService(c.Resolve<ILog>(), assemblies));

this.Container.Register<IFavouritesService>(
c => new FavouritesService(c.Resolve<IStorageService>(), c.Resolve<ILog>()));

And that’s our service registered and resolved, our next step is to inject the favourite service into the VM’s that are going to be using it.

this.Container.Register(
c =>
new MainViewModel(
c.Resolve<INavigationService>(),
c.Resolve<IMessenger>(),
c.Resolve<ILog>(),
c.Resolve<IFavouritesService>()));

this.Container.Register(
c =>
new ChildViewModel(
c.Resolve<INavigationService>(),
c.Resolve<IMessenger>(),
c.Resolve<ILog>(),
c.Resolve<IFavouritesService>()));

A common scenario is that we have a View which provides an experience that allows the user to add items via a list. In my case I choose a context menu which was added to the data template used by the list box. In order to do this I created a Relay Command and wired this up to the context menu from the toolkit in the data template. There are two implementations that we need to implement here first we need to be able to trigger a command from inside the data template of the listbox.

/// <summary>
/// The select add to favourites command.
/// </summary>
private RelayCommand<Person> selectAddToFavouritesCommand;

/// <summary>
/// Gets SelectAddToFavouritesCommand.
/// </summary>
public RelayCommand<Person> SelectAddToFavouritesCommand
{
   get
   {
      return this.selectAddToFavouritesCommand ??
      (this.selectAddToFavouritesCommand = new RelayCommand<Person>(this.ExecuteAddToFavouritesCommand));
   }

   private set
   {
      this.selectAddToFavouritesCommand = value;
   }
}

/// <summary>
/// The execute add to favourites command.
/// </summary>
/// <param name="person">
/// The person.
/// </param>
private void ExecuteAddToFavouritesCommand(Person person)
{
   this.favouritesService.Add(person);
}
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="add to favourites"
Command="{Binding MainViewModel.SelectAddToFavouritesCommand, Source={StaticResource Locator}}"
CommandParameter="{Binding .}" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>

And the other implementation is from an app bar button.

/// <summary>
/// The execute add to favourites command.
/// </summary>
private void ExecuteAddToFavouritesCommand()
{
   this.favouritesService.Add(this.CurrentlySelectedPerson);
   this.NavigationService.Navigate(new Uri("/View/FavouritesPage.xaml", UriKind.Relative));
}
<phone:PhoneApplicationPage.ApplicationBar>
<shell:ApplicationBar IsVisible="True"
IsMenuEnabled="True">
<shell:ApplicationBarIconButton x:Name="AddToFavourites"
IconUri="/resources/icons/appbar.favs.addto.rest.png"
Text="AddToFavourites" />
</shell:ApplicationBar>
</phone:PhoneApplicationPage.ApplicationBar>

<i:Interaction.Behaviors>
<Behaviors:ApplicationBarIconButtonCommand TextKey="AddToFavourites"
CommandBinding="{Binding SelectAddToFavouritesCommand}" />
</i:Interaction.Behaviors>

Lets just quickly circle back, what we have done thus far is implement the required add to favourites functionality, but ultimately we also want to allow the user to view their favourites so next we need to create the page and the favourites VM remembering to add the new VM to the VM Locator.

The favourites VM.

/// <summary>
/// The favourites view model.
/// </summary>
public class FavouritesViewModel : ViewModelBaseWp7
{
    #region Constants and Fields

    /// <summary>
    /// The favourites service.
    /// </summary>
    private readonly IFavouritesService favouritesService;

    /// <summary>
    /// The currently selected person.
    /// </summary>
    private Person currentlySelectedPerson;

    #endregion

    #region Constructors and Destructors

    /// <summary>
    /// Initializes a new instance of the <see cref="FavouritesViewModel"/> class.
    /// </summary>
    /// <param name="navigationService">
    /// The navigation service.
    /// </param>
    /// <param name="messenger">
    /// The messenger.
    /// </param>
    /// <param name="log">
    /// The log.
    /// </param>
    /// <param name="favouritesService">
    /// The favourites service.
    /// </param>
    public FavouritesViewModel(
        INavigationService navigationService, IMessenger messenger, ILog log, IFavouritesService favouritesService)
        : base(navigationService, messenger, log)
    {
        this.favouritesService = favouritesService;

        ThreadPool.QueueUserWorkItem(
            state => { this.MessengerInstance.Register<SelectedPersonMessage>(this, this.OnReceiveMessage); });
    }

    #endregion

    /// <summary>
    /// Gets CurrentlySelectedCurrentlySelectedPerson.
    /// </summary>
    public Person CurrentlySelectedPerson
    {
        get
        {
            return this.currentlySelectedPerson;
        }

        private set
        {
            this.SetPropertyAndNotify(ref this.currentlySelectedPerson, value, "CurrentlySelectedPerson");
        }
    }


    public ReadOnlyObservableCollection<Person> Favourites
    {
        get
        {
            return this.favouritesService.Favourites;
        }
    }

    #region Methods

    private void OnReceiveMessage(SelectedPersonMessage obj)
    {
        if (obj == null)
        {
            return;
        }

        if (obj.Person == null)
        {
            return;
        }

        this.CurrentlySelectedPerson = obj.Person;
    }

    /// <summary>
    /// The is being activated.
    /// </summary>
    /// <param name="storage">
    /// The storage.
    /// </param>
    protected override void IsBeingActivated(IStorage storage)
    {
    }

    /// <summary>
    /// The is being deactivated.
    /// </summary>
    /// <param name="storage">
    /// The storage.
    /// </param>
    protected override void IsBeingDeactivated(IStorage storage)
    {
    }

    #endregion
}

And the favourites page which has a listbox data bound to the favourites collection exposed by the VM.

<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0">
<ListBox ItemsSource="{Binding Path=Favourites}"
SelectedItem="{Binding Path=CurrentlySelectedPerson}"
ItemTemplate="{StaticResource PersonsDataTemplate}"
ItemContainerStyle="{StaticResource ListBoxItemStyle1}" />
</Grid>

And we are done!

One thing that I did add here is the Last Replay Message Messenger and data binding to the selected item in the listbox so that I can highlight the newly added favourite.

A quick recap on what happened

  1. Create the interface for the service and expose what you need usually a collection and some management methods for managing the data
  2. Create the concrete implementation adding in the IStorageService into the constructor
  3. In the concrete add logic to rehydrate and stash your data
  4. Update the constructor on the VM’s that need to have the add to favourites functionality
  5. In the Bootstrapper register the new service and Resolve them in the VM’s
  6. Create relay commands in the VM and View to trigger the actions
  7. Create the favourites page and VM
  8. Update the Bootstrapper and VM Locator with the new VM
  9. Expose a property for the collection so that it can be bound to in the View
  10. [Optional] use the Last Replay Message to inform the favourites VM about the currently selected person

What is very interesting about all this is that we so often when using a framework get familiar with a certain technique and this becomes fashionable and we try to use it everywhere and there is nothing wrong with trying out new ways of doing something. However, there also has to be a realisation that when we start getting friction trying to bend the framework api to do something that it was not really meant for its time to use something solves the problem better.

Interested in your comments and you can find the code up on codeplex.

Be Sociable, Share!

Tags: , ,

2 Responses to “WP7 Contrib – When messaging becomes messy and services shine”

  1. I’ve been working my way through this sample and have two dumb questions…
    What’s the best way to fix the issue where you can’t select the same person twice in a row?

    How would you implement navigating to the favorites page from the app bar independently from the addtofavorites button?

    Thanks,
    Rich

    Rich
  2. Hey Rich,

    Great questions…

    For the currentely selected person property you would need to null this on the way back, you can do this in the VM by using the Navigation Service which is injected into the VM, in the On Navigate you can then set this to be null and this would deselect the item.

    For the app bar there are some v cool binding helpers in the WP7Contrib that can help here, i have actually implemented this in the ChildPage.xaml. This allows you to use commands in this case a Relay Command to be wired up in the same way you would wire a command to a button in Xaml.

    Keep those questions coming, and feel free to update the spikes to suit your needs happy to take a look and we can roll these into the spikes projects which are there so that everyone can benefit. We are making a serious effort to crank out more samples so that people can really get a good idea on the stuff that is in the WP7C.

    Interested in hearing how you get on

    rich

Leave a Reply