Use a custom presenter
-- Me, at least once a week in #mvvmcross
Having been involved in the MvvmCross community for several years now, often it feels like my most common response to questions in the #mvvmcross JabbR room from someone new to the framework is to look into using a custom presenter. Presenters are an important piece of the MvvmCross architecture, but that's not necessarily obvious when you're first getting into things. A long time ago I vowed to do a series of blog posts on different aspects of presenters...better late than never, right?
Eventually I want to dig into a variety of things related to presenters but for now I'd like to keep it to the absolute basics. So, what is a presenter?
What Are Presenters?
Ever wonder what happens in between calling ShowViewModel<>()
in your portable class library and a navigation happening in your app? That's where presenters (and some of their friends) come into play. As the name implies, presenters take requests from the view model layer for things like navigating to a new view model and decide how to present it within the application itself.
Because they deal with the view layer, presenters are specific to individual platforms by definition. This gives you a clean separation between requests and how they get propagated to the UI, which gives you a lot of power. Want to display things differently depending on whether the device is a phone or a tablet? Use tabs on iOS and Android but a pivot view on Windows Phone? Use fragments for everything on Android? Presenters are going to be your new best friend.
The best example I've found to help illustrate this distinction is the typical master-detail pattern. Imagine an app with two view models, one with a list of people, and one for displaying the details of a specific person. On a small phone screen you might want to display the list as a single screen, and then tapping on a person navigates you to a second screen containing their details. If you're on a larger screen like a tablet you might want to use a split view so that both views are actually visible at the same time, and take advantage of the screen's real estate. In both cases the view models and their behavior is identical regardless of how they're displayed. Even the views might even be shared in both implementations, but it's just the actual presentation of those views you want to customize.
Let's take a look at the interface itself for presenters:
public interface IMvxViewPresenter
{
void Show(MvxViewModelRequest request);
void ChangePresentation(MvxPresentationHint hint);
}
Presenters are really useful, but at their core they are also very simple. Let's address each of these interface methods individually.
Show
As the name implies, Show()
is called when ShowViewModel()
is invoked from a view model. Passed into it is a simple object containing the details of this particular request:
public class MvxViewModelRequest
{
public Type ViewModelType { get; set; }
public IDictionary<string, string> ParameterValues { get; set; }
public IDictionary<string, string> PresentationValues { get; set; }
public MvxRequestedBy RequestedBy { get; set; }
// ...abridged...
}
Included here are the type of view model being requested, along with parameters specific to both navigation and presentation. We'll dig into PresentationValues
more in a future post, but that property can be a really useful way to send hints to your presenter to help control how the presenter behaves.
Using the information in this requesst object, it's the presenter's job to construct and display the corresponding view in whatever way makes sense to the UI. For example, a simple presenter on iOS would manage a UINavigationController
and call PushViewController(nextView)
in order to navigate to the requested view. In fact, this is precisely what happens in MvxTouchViewPresenter
, one of the default presenters on iOS and ships in the box with MvvmCross.
ChangePresentation
That covers the basics of showing a new view model, but there's still one other method in IMvxPresenter
: ChangePresention()
. Let's say you want to trigger some sort of presentation change from your view model that doesn't involve showing a new view model. This method is how you can go about doing that.
If you've been working in MvvmCross at all you've probably been using this piece of the presenter without even realizing it. Ever called Close(this)
from a view model? Close()
is a method on MvxNavigatingObject
, the base class to MvxViewModel
, and is really just a quick proxy to ChangePresentation()
:
public abstract class MvxNavigatingObject : MvxNotifyPropertyChanged
{
protected bool Close(IMvxViewModel viewModel)
{
return ChangePresentation(new MvxClosePresentationHint(viewModel));
}
}
ClosePresentationHint
is a built-in class to MvvmCross that can signal to a presenter that it should pop the view, rather than navigate to something new. Let's take the implementation in MvxTouchViewPresenter
as an example:
public override void ChangePresentation(MvxPresentationHint hint)
{
if (hint is MvxClosePresentationHint)
{
Close((hint as MvxClosePresentationHint).ViewModelToClose);
return;
}
base.ChangePresentation(hint);
}
This will then call PopViewController()
in order to navigate backwards in the stack. You could easily create your own subclasses of MvxPresentationHint
in order to signal other actions up to your presenter to achieve whatever behavior your app needs.
Summary
This just scratches the surface of what presenters allow you to do in MvvmCross, but hopefully it helps clarify the piece of the puzzle they provide. Navigation can get particularly tricky when you try to take it cross-platform, so in future posts I'll try to cover some things I've run into there, as well as some other presenter tricks of the trade.
Have something in particular you want to know about presenters? Let me know!