Hi all SO viewers. I am normally an Android developer, but now I'm developing a cross platform application targeting WPF and Android. That being said, there's practically no info on how to directly do what I want. So, I ended up finding a 3-part blog series that goes in depth on how to develop a Windows-based cross platform MVVM project. As long as I set the PCL to be compatible with Xamarin.Android, any code that doesn't throw an error SHOULD work once I get to the Android side of things. Here are the links to the blog posts: Blog 1, Blog 2, Blog 3. Again, I do Android, so I am new to doing coding for a WPF Application.
So my issue today is only dealing with the PCL-WPF side which relates to the above-linked blog post. I followed every single step laid out in the posts as best as I could. The blog uses WinRT and WinPhone as the two target platforms, so I HAD to try figuring out things on my own to make things work on the WPF. Two of the things I had to do was use IsolatedStorage
and basically use the WinPhone App.Xaml
to make the WPF side build.
I have finished the blog all the way to the end and build succeeds. I even can see my example Debug lines like it talks about at the end of the third blog post. However, when I go to run it, I get the following:
ActivationException was unhandled by user code
An exception of type 'Microsoft.Practices.ServiceLocation.ActivationException' occurred in GalaSoft.MvvmLight.Extras.dll but was not handled in user code
$exception {Microsoft.Practices.ServiceLocation.ActivationException: Type not found in cache: StackOverF.Services.IStorageService. at GalaSoft.MvvmLight.Ioc.SimpleIoc.DoGetService(Type serviceType, String key, Boolean cache) in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 537 at GalaSoft.MvvmLight.Ioc.SimpleIoc.GetService(Type serviceType) in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 789 at GalaSoft.MvvmLight.Ioc.SimpleIoc.MakeInstanceTClass in c:\MvvmLight\Source\GalaSoft.MvvmLight\GalaSoft.MvvmLight.Extras (PCL)\Ioc\SimpleIoc.cs:line 729} System.Exception {Microsoft.Practices.ServiceLocation.ActivationException}
Is there anything that you guys can tell me that maybe the blog author skipped over that I need to do? Maybe if enough rocks are thrown at this "boulder," it'll crack open...
There are basically currently only two projects in my Visual Studio Solution. One is the Portable Class Library. The other is the WPF Application. In the very near future, once I get things working on the WPF side of the equation, I'll use the PCL in Xamarin to reuse the code in an Android project. However, the Android side is not part of my problem here. I'm having the above issue when only dealing with the WPF project.
IMainViewModel.cs
using GalaSoft.MvvmLight.Command;
using StackOverF.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.ViewModels {
public interface IMainViewModel {
ObservableCollection<Workload> Workload { get; }
RelayCommand RefreshCommand { get; }
RelayCommand AddCommand { get; }
}
}
MainViewModel.cs
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using StackOverF.Models;
using StackOverF.Services;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.ViewModels {
public class MainViewModel : ViewModelBase,IMainViewModel {
private IDataService dataService;
public MainViewModel(IDataService dataService) {
this.dataService = dataService;
RefreshAsync();
}
private ObservableCollection<Workload> workload = new ObservableCollection<Workload>();
public ObservableCollection<Workload> Workload {
get {
return workload;
}
}
#region Commands
#region Refresh
private RelayCommand refreshCommand;
public RelayCommand RefreshCommand {
get {
return refreshCommand ?? (refreshCommand = new RelayCommand(async () => { await RefreshAsync();}));
}
}
private async Task RefreshAsync() {
workload.Clear();
foreach (Workload listing in await dataService.GetWorkloadAsync()) {
workload.Add(listing);
}
}
#endregion
#region Add
private RelayCommand addCommand;
public RelayCommand AddCommand {
get {
return addCommand ??
(addCommand = new RelayCommand(async () => {
Workload listing = new Workload() { Id = 3, Serial = "relay12" };
await dataService.AddWorkloadAsync(listing);
workload.Add(listing);
}));
}
}
#endregion
#endregion
}
}
LocatorService.cs (DeviceLocatorService, located in WPF Project)
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.Services {
public class DeviceLocatorService {
static DeviceLocatorService() {
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic) {
}
else {
}
if (!SimpleIoc.Default.IsRegistered<IStorageService>())
SimpleIoc.Default.Register<IStorageService, StorageService>();
}
public static void Cleanup() {
}
}
}
LocatorService.cs (LocatorService, located in PCL Project)
using Microsoft.Practices.ServiceLocation;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using StackOverF.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StackOverF.Services {
public class LocatorService {
static LocatorService() {
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
// Services
if (ViewModelBase.IsInDesignModeStatic) {
SimpleIoc.Default.Register<IDataService, Design.DataService>();
}
else {
SimpleIoc.Default.Register<IDataService, Services.DataService>();
}
// View Models
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
}
public IMainViewModel MainViewModel {
get {
return ServiceLocator.Current.GetInstance<IMainViewModel>();
}
}
public static void Cleanup() {
}
}
}
It errors (WHILE DEBUGGING ONLY) on the return ServiceLocator.Current.GetInstance<IMainViewModel>();
line.
App.xaml
<Application x:Class="StackOverF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:services="clr-namespace:StackOverF.Services;assembly=StackOverF.PCL"
xmlns:deviceServices="clr-namespace:StackOverF.Services"
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<deviceServices:DeviceLocatorService x:Key="Locator.WPF" d:IsDataSource="True" />
<services:LocatorService x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
</Application>
The solution to my problem is simpler than one might expect. WPF
applications have no problem using MVVM
toolkits/frameworks, but they do seem to have a problem with sharing. Since WPF
wasn't intended to be a cross-platform-friendly language, the "correct" way to program something like this won't work for it.
The issue comes around trying to include both LocatorService
classes in the App.xaml
and expecting WPF
to run both classes like WinRT
or WinPhone
might. WPF
seems to only refer to a class if the data is needed. Just like in the blog's example, I had in the Main.xaml
data bound to the LocatorService
class. Since the WPF
application only ran that class's code, it'd throw the error.
The solution was to combine the LocatorService
files into one file, on the WPF
project side. Why on the WPF
side you ask? A Portable Class Library
should only contain universal code, shareable cross platform.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With