A question came in today regarding the use of design time data in conjunction with Mvvmcross. My initial response was “it just works” because I assumed that everyone does design time data in Blend….. and I’m still not sure why you won’t do this, since it promotes better design by giving designers the freedom to dictate the information architecture of each page. As it happens what they were really after was a way to plug in design time data that is generated in code. Without using mvvmcross this was relatively easy using a ViewModelLocator pattern – as part of serving up the view models, you could simply change the data source to be design time data when running in the dev tools. Because mvvmcross abstracts a lot of the work involved in associating pages with view models, it also means that it’s a bit harder to wire up coded design time data. In this post I’ll provide a summary (read “crude”) way I found to do this:
The assumption here is that your Mvvmcross project follows how Stuart describes a typical project in any of his getting started videos. Essentially this means a UI project (eg a Windows Phone project), that would contain your views (eg pages) and a Portable Class Library, that would contain your view models. There is also an App class in the PCL which is used to initialise mvvmcross. In this example we’re just going to work with the FirstView and its corresponding FirstViewModel but we’re going to add in support for a service which is used to populate data. Here’s the FirstViewModel:
public class FirstViewModel
: MvxViewModel
{
private IDataService Data { get; set; }
public FirstViewModel(IDataService data)
{
Data = data;
}
public string Hello
{
get { return Data!=null?Data.TestData:"Missing"; }
}
}
The IDataService has two implementations: DataService and DesignData, as illustrated here:
public interface IDataService
{
string TestData { get; }
}
public class DesignData : IDataService
{
public string TestData
{
get
{
return "Designtime";
}
}
}
public class DataService:IDataService
{
public string TestData
{
get
{
return "Runtime";
}
}
}
At runtime we don’t need to worry about creating an instance of the DataService as it is automatically picked up by mvvmcross and injected into the FirstViewModel constructor. However, the DesignData is a little harder to use. At design time we somehow have to coerce mvvmcross to generate the FirstViewModel and populate it with an instance of DesignData. One option would be to go down the ViewModelLocator path again and use it only at design time to create our view model using design time data. However, this is rather clumsy given we have a perfectly good DI framework that we can leverage. The other thing is we’d have to continually update it to expose properties for each view model we want.
An alternative is to use a Factory pattern coupled with a Converter to dynamically create the appropriate view model. This is a bit of a chicken-and-egg thing to describe so I’ll start with how we wire it up in the UI project. Firstly, in the App.xaml we need to create both the Factory and Converter as resources:
<Application.Resources>
<codedDesignTimeData:DesignFactory x_Key="Factory"/>
<codedDesignTimeData:DesignTimeConverter x_Key="DesignConverter" />
</Application.Resources>
Next, in the page we set the d:DataContext (ie the design time data context) as follows:
<views:MvxPhonePage
x_Class="CodedDesignTimeData.Views.FirstView"
d_DataContext="{Binding Source={StaticResource Factory}, Converter={StaticResource DesignConverter}, ConverterParameter=FirstViewModel}"
… >
Let’s read through what this is saying – at design time, the DataContext for this page is going to be the Factory, passed through the Converter with the parameter “FirstViewModel”. As you can imagine what we’re really saying is that we’re asking the Factory to produce an instance of the FirstViewModel that we can data bind to.
Ok, so now we know what it’s saying, let’s look at the implementation of firstly the Converter, and then the Factory itself:
public class DesignTimeConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var typeName = parameter as string;
if (string.IsNullOrWhiteSpace(typeName)) return null;
var factory = value as DesignFactory;
return factory.Create(typeName);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
There isn’t much to the converter, it simply passes the parameter (ie the type of the view model to be created) into the Create method on the Factory object.
public class DesignFactory
{
private Core.App app;
public object Create(string typeName)
{
if (app==null)
{
MvxSimpleIoCContainer.Initialize();
app = new Core.App();
app.Initialize();
Mvx.RegisterType<IDataService,DesignData>();
}
var type = app.FindViewModelTypeByName(typeName);
if (type == null) return null;
var req = MvxViewModelRequest.GetDefaultRequest(type);
if (req == null) return null;
var locator = app.FindViewModelLocator(req);
if (locator == null) return null;
IMvxViewModel vm;
if (locator.TryLoad(type, null, null, out vm))
{
return vm;
}
return null;
}
}
The factory is where all the smarts are. The first thing we do is make sure that mvvmcross is initialized – this is similar to what happens in your app. We then call FindViewModelTypeByName (see below for implementation) to exchange the view model type name with a reference to the actual type. Next we invoke the view model locator infrastructure within mvvmcross to retrieve an actual instance to the view model we’re after, which we then return (and is subsequently set as the DataContext for the page).
public Type FindViewModelTypeByName(string typeName)
{
return CreatableTypes().FirstOrDefault(t => t.Name == typeName);
}
Ok, but wouldn’t is just be better to create an instance of the view model, once we have a reference to the type? No, because that view model may have any number of dependencies, which is why we need to rely on the DI framework provided by mvvmcross. You’ll notice I skipped over one quite important line, which was that we register the type DesignData as the implementation of IDataService we want to use. By default the Initialize method on our App class will look for types that end in “Service” and register them, which is why our DesignData doesn’t end in “Service”, but it also means we have to manually register it.
The upshot of this is that an instance of the DesignData class will be used to service up design time data for our view models in Blend. I hope this helps anyone working with mvvmcross.