WP7 Background Loading with Caliburn and Coroutines

Standing on the shoulders of giants doesn’t even come close. I’m just a hack bringing together some of the wonderful special sauce and ground unicorn horn. This is merely a recipe.

Back in the day (heck, over 6 months ago), Keith Patton pointed to some guidance from Microsoft about the best time to do any heavy-lifting in your Windows Phone 7 apps. The best thing is to wait for the first call to LayoutUpdated after the page has completed navigation. Since then, I’ve also had a good read of Rob Eisenberg‘s simply jaw-dropping documentation around Caliburn Micro. Caliburn Micro is (in my humble opinion), the best MVVM framework for Windows Phone 7, and possibly the best MVVM framework bar none.

[box type=”info”]As if Caliburn wasn’t amazing enough, Rob has responded to my original post and given some brilliant tips that resulted in a bunch of refactoring. You get to see the final product.[/box]

Until we get C# 5 on the phone, Rob’s coroutines are a pretty damn sweet way to bring together multiple asynchronous calls in a very tidy syntax. The heavy-lifting we need to do in a WP7 page load almost invariably involves async calls to load data (especially after tombstoning), plus some user notification. Coroutines eat this stuff for breakfast.

So how do we roll this all together? What I wanted was a way for any given page to do this:

  1. Load up and bind to the relevant view model;
  2. All pages should implement the “After first layout updated” pattern;
  3. If the page’s view model knows about my heavy-lifting paradigm, then use it;
  4. Do it sexily with coroutines, which keeps the code tidy and helps us with user notifications in asynch-world.

Step 1

Use Caliburn Micro. It’s just that easy. If you’re still manually setting DataContext in your XAML pages (without a good reason), you’re doing it wrong.

Step 2

To make sure all my pages implement the pattern, I created a custom FrameAdapter based on the Caliburn one. Previously I had used a custom base page, but going the FrameAdapter route means we don’t have to munge the machine generated xaml when we create a new page.

There’s a couple of interesting bits in here:

  • On the Frame’s navigated override, we hook up the Content’s LayoutUpdated event. The content of the frame is our xaml view.
  • We check if the pages’s DataContext is IFirstLayoutAware.
  • We use the brand-new Action.Invoke method from Caliburn Micro, which creates and execution context for us, then executes the named method (which we’ve defined as required in IFirstLayoutAware.)
public class LayoutAwareFrameAdapter : FrameAdapter 
{
	private bool _onNavigatedToCalled;
	private readonly Frame _frame;

	public LayoutAwareFrameAdapter(Frame frame, bool treatViewAsLoaded = false) : base(frame, treatViewAsLoaded)
	{
		_frame = frame;
	}

	protected override void OnNavigated(object sender, NavigationEventArgs e)
	{
		var fe = e.Content as FrameworkElement;
		if (fe != null)
		{
			fe.LayoutUpdated += PageLayoutUpdated;
		}
		_onNavigatedToCalled = true;
		base.OnNavigated(sender, e);
	}

	void PageLayoutUpdated(object sender, EventArgs e)
	{
		var fe = _frame.Content as FrameworkElement;

		if (fe == null || fe.DataContext == null)
			return;

		if (_onNavigatedToCalled && (fe.DataContext is IFirstLayoutAware))
		{
			_onNavigatedToCalled = false;

			var dc = fe.DataContext as IFirstLayoutAware;
			Action.Invoke(dc,"AfterLayoutFirstUpdated",fe,fe);
		}
	}

}

To take advantage of this custom frame adapter, we need only inject it in our Caliburn Bootstrapper, in place of the default FrameAdapter. I’m using Ian Randall’s MicroIoc container here. Line 12 is the only change required to wake up the functionality in the class above.

public class AppBootstrapper : PhoneBootstrapper
{
	IMicroIocContainer container;

	protected override void Configure()
	{
		TiltEffect.SetIsTiltEnabled(RootFrame, true);

		container = new MicroIocContainer()
			.Register<MainPageViewModel>("MainPageViewModel")
			.Register<ItemListViewModel>("ItemListViewModel")
			.Register<ItemDetailViewModel>("ItemDetailViewModel")
			.Register<PlaceBidViewModel>("PlaceBidViewModel")
			.RegisterInstance<INavigationService>(new LayoutAwareFrameAdapter(RootFrame), null)
			.RegisterInstance<IPhoneService>(new PhoneApplicationServiceAdapter(PhoneService), null);
	   
	}
	
	[...]
}

Step 3

You would have seen the IFirstLayoutAware interface in the previous step. This is just a tiny interface that defines the behaviour required of View Models that will take advantage of our performance approach:

public interface IFirstLayoutAware
{
	IEnumerable<IResult> AfterLayoutFirstUpdated();
}

The IEnumerable<IResult> type will be familiar if you’ve read the documentation around Coroutines. I you haven’t, you need to do so right now before you read on. I’ll wait.

Step 4

So, within any of your View Models that implement IFirstLayoutAware you need to implement that special method. How you do it will depend on how you’ve chosen to divide up the loading behaviour for your application, but here’s mine for the first page of my Silent Auction app:

public class MainPageViewModel : BusyIndicatorViewModelBase, IFirstLayoutAware
{
	[...]

	public IEnumerable<IResult> AfterLayoutFirstUpdated()
	{
		using (this.Block("Loading Auction Settings..."))
		{
			yield return new LoadSettings(SettingsAction.GetApiHost);
			yield return new LoadSettings(SettingsAction.GetSettings);
		}
		
		_canBrowseItems = true;
		//ping all properties
		Refresh();
		// can do this after page is visible...
		yield return new LoadAuctionItems();
	}
}

Isn’t it gorgeous? Those yield returns are sent to Caliburn’s Action pipeline, and executed in turn. There’s a lot of custom code behind each one, but just quickly:

  • LoadSettings does some asynchronous calls to a web API to get some basic information.
  • Once LoadSettings is complete, we can turn off the loading message and refresh some data on the page via Refresh(); (e.g. links to brand images that we have just loaded from settings).
  • LoadAuctionItems is less urgent (we need it for the next page), so it can happen last, and just chug away in the background while the user is thinking.

Remember that all of the guidance around Silverlight and WP7 still apply. Inside those IResult objects we should be using proper asynchronous coding – don’t block on web requests; marshall callbacks to the UI thread; and all that good stuff.

The using(this.Block(..)){} pattern is another gem from Rob Eisenberg. Block is an extension method that returns a DisposableAction, which in itself is a sweet little pattern. It says “give me an object that will execute a defined action when it is disposed”:

public class DisposableAction : IDisposable
{
	Action _action;

	public DisposableAction(Action action)
	{
		if (action == null)
			throw new ArgumentNullException("action");
		_action = action;
	}

	public void Dispose()
	{
		_action();
	}
}

So in our particular case of using (this.Block(“Loading Auction Items…”)) we’re saying “set a busy message, and when I’m finished, clear it out.” The Block extension method is below.

public static class BusyExtensions
{
	public static IDisposable Block(this IBusyIndicator indicator, string message)
	{
		indicator.ShowBusy(message);
		return new DisposableAction(() => indicator.HideBusy());
	}
}

Your view models that want to show busy messages need to implement IBusyIndicator, which is something a bit like this:

public interface IBusyIndicator
{
	void ShowBusy(string message);
	void HideBusy();

	bool IsBusy { get; set; }
	string BusyMessage { get; set; }
}

Conclusion

Like I said at the start, this is just a recipe, made up of many parts magical Caliburn fairy dust, and some special sauce. You’ll need to understand MVVM and Caliburn to really make the most of it. Let me know if you like it.

7 Replies to “WP7 Background Loading with Caliburn and Coroutines”

  1. Very good stuff. Let me make a couple of suggestions. Someone recently reminded me that using blocks work fine inside of iterators, so you might consider re-writing your coroutine to something like this:

    public IEnumerable AfterLayoutFirstUpdated() {
    using(Loader.Show(“Loading auction settings…”)) {
    yield return new LoadSettings(SettingsAction.GetApiHost);
    yield return new LoadSettings(SettingsAction.GetSettings);
    }

    _canBrowseItems = true;
    Refresh();

    yield return new LoadAuctionItems();
    }

    If you are inheriting from PropertyChangedBase or one of its derivatives, you can also use the Refresh method in place of the call you have above.

    Another thought relates to the base view. You might consider hooking into the navigation service and applying this behavior via wiring to events on the page being navigated to. Then, you won’t need a custom base class.

    1. Thanks for the suggestions. The using block is tidy – so you’d manage the hide of the load message in the Dispose call?

      I’ll look into the navigation service stuff thanks!

    2. Correct me if I’m wrong, but with the using block I think we need to do this:

      var loader = Loader.Show(“Loading auction settings…”);
      yield return loader;

      using(loader) {
      yield return new LoadSettings(SettingsAction.GetApiHost);
      yield return new LoadSettings(SettingsAction.GetSettings);
      }

      ..otherwise, where does the Loader get emitted to the iterator?

      1. My recommendation in the case of the using would be to not use an IResult for the using, but to use a busy service directly, possible injected through the ctor. It might look like this:

        interface IBusyService{
        void ShowBusy(string message);
        void HideBusy();
        }

        You could then create an extension method which looks something like this:

        public static class BusyExtensions{
        public static void Block(this IBusyService service, string message){
        service.ShowBusy(message);
        return new DisposableAction(() => service.HideBusy());
        }
        }

        DisposableAction is a class that encapsulates a procedure which it calls when disposed. In this way you can have some nice syntax on top of the service API.

Leave a Reply

Your email address will not be published. Required fields are marked *