Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVVMLight and platform specific service implementations

Tags:

c#

mvvm-light

What I have

Using MVVMLight, I have some service interfaces that are declared in a portable project and the corresponding implementations in a Windows Phone project (WP8.1 SL). To register the implementations I use SimpleIoc.Default.Register in the Application_Launching method within the App class.

public partial class App : Application
{
    ... 

    private async void Application_Launching(object sender, LaunchingEventArgs e)
    {
        ...

        // add some platform specific services to the IOC container
        SimpleIoc.Default.Register<INavigationService, NavigationServiceWP>(true);
        SimpleIoc.Default.Register<ISettingsService, SettingsService>(true);
        SimpleIoc.Default.Register<IThemeService, ThemeService>(true);
        SimpleIoc.Default.Register<IGeofenceService, GeofenceService>(true);
        SimpleIoc.Default.Register<IVersionService, TrialInformation>(true);
        SimpleIoc.Default.Register<IPhoneService, PhoneServices>(true);

        ...
    }

    ...
}

The view model locator is located in the portable project and registers all view models to the IOC container in a static constructor, just as the docs say.

static ViewModelLocator()
{
    ...

    SimpleIoc.Default.Register<TagColorViewModel>();

    ...
}

The TagColorViewModel is one of these models. It receives a message before the corresponding view is shown. For example, when a tag is clicked to change its color then MessengerInstance.Send is used, and afterwards the navigation service is used to navigate to the tag color change view.

// TagViewModel
private void ChangeColor()
{
    MessengerInstance.Send(Tag, TagColorViewModel.MessengerToken.SetTag);
    _navigationService.Navigate("/UI/Tagging/TagColor.xaml");
}

This message receiver is registered in the constructor.

// TagColorViewModel
[PreferredConstructor]
public TagColorViewModel(INavigationService navigationService)
{
    ...

    // Messages
    MessengerInstance.Register<Tag>(this, MessengerToken.SetTag, SetTag);
}

The Actual Problem

Because the view model is created in MVVMLight right before it is used for the first time via its corresponding view, the message is not received by TagColorViewModel (simply because there is no instance of that VM yet).

A possible solution would be to use true as parameter when registering the view model.

SimpleIoc.Default.Register<TagColorViewModel>(true);

This unfortunately doesn't work. The reason is that, as can be seen in the constructor above, TagColorViewModel has a dependency to the INavigationService. This in turn is registered in the Application_Launching method, which is after the static constructor of the view model locator is called. The result is that SimpleIoc can't create an instance of TagColorViewModel because there is no known INavigationService interface or implementation.

Actual Question

How can I solve this issue? In other words: How can I register platform specific services in MVVMLights SimpleIoc so that I can let it create instances of view models during their registration?

Xamarin seems to use a decorator to solve such issues but I don't know of any comparable construct in MVVMLight.

Xamarin.Forms.Dependency(typeof(PopupService))

Current Workaround

My current workaround is to get an instance that is never used later right after all the platform specific services are registered. It works but I don't think this is the correct solution to that.

private async void Application_Launching(object sender, LaunchingEventArgs e)
{
    ...

    // add some platform specific services to the IOC container
    SimpleIoc.Default.Register<INavigationService, NavigationServiceWP>(true);
    ...

    var tcvm = SimpleIoc.Default.GetInstance<TagColorViewModel>();

    ...
}
like image 355
Stephan Avatar asked May 25 '15 21:05

Stephan


1 Answers

Use a combination of Xamarin's Dependency Service and MVVMLight. MVVMLight's Register method has an overload where you can write your own function to generate the instance.

Put all of your Service Registrations back into ViewModelLocator like this:

SimpleIoc.Default.Register<Service.INavigationService>(() => {
     return DependencyService.Get<Service.INavigationService>();
});

Then go and put the appropriate assembly attributes on your platform-specific implementation of the services...

[assembly: Xamarin.Forms.Dependency(typeof(NavigationService))]
like image 174
Lee McPherson Avatar answered Oct 05 '22 22:10

Lee McPherson