Everyone is excited about the new Universal Apps. I can now have a new type of project, called a Shared project, where I can put all the common files shared between a Windows 8.1 and Windows Phone 8.1 project. This is a better way around the “Add as a Link” option we had before. But universal apps is more than just a shared project. It seems that the APIs used by both Windows and Windows Phone are converging into one. Granted there still a lot of work to do and I’m not sure if there will be a 100% compatibility between the two frameworks, but this is a great step.

I’m going to talk about how to localize a universal app. In my article Internationalization of a Cross Platform Application I explain how to use the Multilingual App Toolkit in a PCL project to localize an application targeted for multiple platforms (Windows, Windows Phone, Android and iOS). But when I tried to apply the steps in the article to a Universal App I run into some problems. I will still base my localization on resource files, the thing is where do I put these? Should I put them in the new Shared project? or should I keep using PCL projects? Let’s first review our main goals:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects.
  • Strings can be used in the UI as well as programmatically
  • Use the Multilingual App Toolkit to handle all the translations


Resource files in Shared project

When I knew about the new shared project type I thought “great, now I can put my resource files in the shared project”. Well, it turns out you can, but this is not the best approach. To explain why let’s see how our goals stand when we use resource files in shared projects.

As mentioned before, universal apps are basically three projects: a project for Windows 8.1, a project for Windows Phone 8.1 and a Shared project. By putting resource files in the shared project they will be shared by both the Windows and Windows Phone projects. Our first goal is ok.

The convergence of the frameworks between Windows and Windows Phone is also applied to the use of resources. Windows apps (aka Windows Store or WinRT apps) use the .resw resource type. On the other hand, Windows Phone Silverlight apps (the “Silverlight” suffix was added to differentiate them from the new Windows Phone apps which use the WinPRT framework) use .resx resource files. Now, both Windows 8.1 and Windows Phone 8.1 use .resw files..resw files can be used in XAML by specifying the x:Uid attribute in XAML components (this was not possible with .resx files), but these resource files do not have a strong type associated to them (as in the case of .resx resource files). This means that we cannot use resource strings programmatically. You can create a strong type associated to .resw files by using ResW File Code Generator, but it turns out that this extension doesn’t work if the resource is in the shared project. So, our second goal doesn’t stand.

The Multilingual App Toolkit cannot be enabled for shared projects. You have to enable MAT in both Windows and Windows Phone projects. It turns out that the decision of the MAT team regarding universal app was that they “decided to enable MAT based on the specific project instead of the shared project” (see this blog post for details). This means that I would have to do translations twice, one for each project. When you enable MAT in both projects you’re solution will be something like this:

Universal app with MAT

.xlf files are duplicated. So, yes we can use MAT on universal app (our third goal is ok) but since we can’t use it on the shared project our first goal doesn’t stand.

Finally, our goals stand as this:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects: No
  • Strings can be used in the UI as well as programmatically: No
  • Use the Multilingual App Toolkit to handle all the translations: Yes

In summary, having a resource file in the shared project means that I will not be able to use resource strings programmatically and also that I will have to deal with duplicate translations and manual steps to keep these translations up to date. So, resource files in shared projects: not the best approach.


Resource files in PCL project

Our second option is to put resource files in a PCL project. Let’s see how this options stands with our goals. You can perfectly add a PCL project to a universal app solution:

PCL for universal app

When you do this Visual Studio automatically sets the targets for the PCL project to “Windows 8.1” and “Windows Phone 8.1”:

PCL project properties

I suggest to change the “Default namespace” of the PCL project to match the default namespace of the other projects as well. In the PCL project, create a folder structure that is compatible with the project’s default language, in my case “en-US”. Add a new resource (.resw) file to the folder. You should have something like this:

PCL folder structure

(note: you can change the PCL default language by editing the PCL project file. Look for the node DefaultLanguage)

It is required for the resource file to be inside a folder with a name that matches the project default language, otherwise MAT will not find any strings and the .xlf files will not be generated.

Since the targets for the PCL are “Windows 8.1” and “Windows Phone 8.1” the resource files you can add are of type .resw. We already explained that .resw resources do not have an associated strong type and we cannot use strings programmatically. To work around this we will use ResW File Code Generator: in the .resw file properties we will specify values for the properties “Custom Tool” and “Custom Tool Namespace” to be “ReswFileCodeGenerator” and the namespace of our project, respectively:

resw file properties

This will generate a strong type associated with the resource file at compile time.

You can follow the approach explained in How to use the Multilingual App Toolkit (MAT) in a Portable Class Library (PCL) project to see how to use MAT on a PCL:

  • Enable MAT for the PCL project
  • Add translation languages to the project (in my case I’m adding “es” and “it”)

Your PCL project should now look as this:

PCL with MAT

At this point, when you compile the PCL project, all the strings in the default resource file will be copied to the .xlf files by MAT. You can now start translating using MAT.

There are some steps we need to do in order to finish this:

  • Make sure you add the PCL project as a reference to both Windows and Windows Phone projects
  • In the shared folder, create a structure such as the following:

Shared folder structure

Basically you need to create a resource file for each language you support and this resource file needs to be inside a folder with the name of the locale. The .resw files in this shared folder structure can be empty. This is a requirement of both Windows and Windows Phone projects in order to know that the application supports all those languages. At this point you can start using resource strings in the UI as well as programmatically (see more details about how to use PCL resources in cross platform apps by reading the article Internationalization of a Cross Platform Application)

Let’s review if we have accomplished our goals:

  • To have a single place where we can specify strings and have these strings shared by all the platform projects: Yes
  • Strings can be used in the UI as well as programmatically: Yes
  • Use the Multilingual App Toolkit to handle all the translations: Yes


Sample App

You can find a sample VS project here:


Conclusion

In summary, having our resources in a PCL project is still the best option. Shared projects is an excellent idea, but it still lacks support for some other features, as in this case, to be able to handle resource files using MAT and to have .resw files generate a strong type. Let’s hope that the guys in the MAT team provide a way to use MAT in shared projects in the future. In the meantime, let them know that we need this by voting on the user voice idea.

8 Comments

The use of PCL projects and paradigms such as MVVM allow for a great code sharing between all the different platform projects. The recently announced shared projects in Visual Studio 2013 Update 2 and the support of shared projects by Xamarin improve the code sharing when developing for multiple platforms. I’ve been using Xamarin tools for Visual Studio to create cross platform applications for mobile devices, but this is not enough. If you truly want to use MVVM, data binding, etc. then you need to resort to complementing frameworks. I’ve been using MvvmCross, as it is one of the most complete and mostly adopted frameworks for using MVVM in non-windows platform developments (such as in iOS and Android) with Xamarin.

MvvmCross is an excellent framework and library. It provides nuget packages for the core and a set of plugins for platform specific functionality. It’s very easy to adopt and understand, thanks to the great tutorials published by its author, Stuart Lodge, in its N+1 series. In all, is a must if you want to create cross platform applications. However, there is one thing I don’t like about MvvmCross: internationalization support.

Internationalization of applications using MvvmCross (explained in this N+1 series video) in my opinion has the following shortcomings:

  • Looks like a proprietary (non-standard) way of implementing internationalization
  • It is based on platform specific plugins
  • Localization resources are defined in the platform projects and not in the PCL project. This means that you will have to repeat the strings and localization on each platform.
  • It is based on JSON (not that I don’t like JSON, but I don’t like it when used in something that it is not standard)
  • There are no tools that will allow to easily translate the strings

Stuart, at the end of the video, suggests that there might be other ways to do internationalization, by using resource files. Resource files are supported by Visual Studio and also by Xamarin. However, resource files are not supported in the platform specifics. At the end of the video Stuart points at this url for an alternative approach to internationalization using resource files. After reading the article I was not satisfied, the article was not clear and I had a difficult time following it. I thought that there must be a better approach to it.

I wanted a solution to internationalization that complies with the following guidelines:

  • It must be based on resource files
  • Resource files should be maintained in only one project
  • I should be able to use the excellent Multilingual App Toolkit tool to maintain the translation
  • It should use the platform specific way of implementing internationalization

Finding a solution to comply with all of the above guidelines was not easy, so I wanted to share the findings so other people will save time digging in the internet for a better solution to internationalization.

I will use a solution such as the one used in the article A sample cross platform application using MVVM, but you can use any existing solution that has a PCL project and platform specific projects for WinRT, Windows Phone, iOS and/or Android.


Using the Multilingual App Toolkit in a PCL project

I found this article on the internet that explains how to use the Multilingual App Toolkit in a PCL project. The article explained what I wanted to accomplish, but it didn’t explain all that needs to be done in order to make it work. It lacks some important steps to actually have it all working. The article also shows part of the whole solution: it only explains the internationalization for WinRT and Windows Phone platform and doesn’t explain how to extend this to iOS and Android projects. I suggest you read the article since it provides a good explanation of the difference of resource files between PCL, WinRT and Windows Phone. Following is a more detailed explanation of how the use the Multilingual App Toolkit in a PCL project

I’m assuming that you have already installed the Multilingual App Toolkit extension in your Visual Studio. If you haven’t please do install it before continuing.

We will use the Windows Phone project as the main project for our internationalization work. The reason is because Windows Phone and PCL projects use the same .resx files for resources, and also because it is much easier to use the Multilingual App Toolkit from a Windows Phone project.

Resource file created as part of the WP8 projectWhen you create a Windows Phone project, the Visual Studio template includes a Resources\AppResources.resx resource file. We will add a copy of this resource file in the PCL project since this will need the resource file as part of its file structure. We will then remove the Windows Phone resource file and add a link to the resource file defined in the PCL.

Follow these steps:

  • In the PCL project, create a Resources folder
  • Add a copy of the AppResources.resx file located in the Windows Phone Resources folder
  • Make sure that the custom tool used for this added file is PublicResXFileCodeGenerator, and that the resource file has also a designer file associated with it.
  • Delete the AppResources.resx file from the Resources folder of the Windows Phone project
  • In the Windows Phone project, in the Resources folder, add a link to the AppResources.resx file of the PCL project
  • Make sure that the custom tool used for this linked file is empty
  • In the Windows Phone project, delete the file called LocalizedStrings.cs

Your solution structure should now look as this:

Solution structure after performing the steps

Now, follow these steps:

  • With the Windows Phone project selected, in Visual Studio menu go to “Tools” and then select “Enable Multilingual App Toolkit”. This will add the pseudo language to the project (which you can delete if you want to).
  • Right click on the Windows Phone project and from the menu select the “Add Translation Language…" option. This will open the Multilingual App Toolkit dialog to add languages to your project.
  • Add as many languages as you need. In my case, and for this example, I’m adding Spanish and Italian (no culture specific)
  • In the PCL project, select the option to “Show All Files” on the Solution Explorer
  • In the PCL project, refresh the Resources folder. You should see a .resx file for each of the added languages.
  • Select all then .resx files and then right click and choose “Include in Project”. This will add those resources files to the PCL project.

Your solution structure should now look as this:

Solution structure after adding the new languages

Now, we need to add a service to our PCL project to provide translated strings. Follow these steps:

  • In the PCL project, open the default resource file, Resources\AppResources.resx, and change the access modifier from “Internal” to “Public”

Access Modifier

  • In the Services folder, add a new class called ResourceService.cs and replace the code with the following:
using SampleApp.Resources;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public class ResourceService
    {
        public static AppResources Resources;

        public AppResources LocalizedResources
        {
            get
            {
                return Resources ?? (Resources = new AppResources());
            }
        }
    }
}
  • On the Windows Phone project, open App.xaml and replace the following line:
<local:LocalizedStrings xmlns:local="clr-namespace:SampleApp" x:Key="LocalizedStrings"/>

with this line:

<services:ResourceService x:Key="LocalizedStrings" />

make sure you define the services namespace to point to the PCL assembly.

  • On the WinRT project, open App.xaml and add the following resource:
<services:ResourceService x:Key="LocalizedStrings" />

make sure you define the services namespace to point to the PCL assembly.

Now, we need to configure the projects. Follow these steps:

  • In the Windows Phone project, right click and choose “Properties” to see the project properties. In the “Application” tab, make sure that the section “Supported Cultures” list all the languages available for the application.
  • In the WinRT project, open the manifest file, Package.appxmanifest, and in the “Application” tab make sure that “Default language” is set to the same language used as your default language in the Windows Phone project.

Now you can start using the Multilingual App Toolkit to translate your resources. As an example, let’s create a new string:

  • In the Windows Phone project, double click the Resources\AppResources.resx file
  • Add a new string (in this example “users”) and its translation to the default language (in this example “en”)

Resource file

  • Compile the solution
  • The Multilingual App Toolkit will update the .xlf files, and also update the .resx files, which are located in the PCL project file structure.
  • On the Windows Phone project, double click any of the .xlf files to open the Multilingual Editor and start doing the translation of the new strings

Multilingual editor

At this point we have the following:

  • All resource files are in the PCL project
  • The main resource file is edited from the Windows Phone project
  • We can use the Multilingual App Toolkit, from the Windows Phone project, to edit translations
  • We have a service in the PCL project that will provide translated strings for all projects

Let’s see now how can we use this to translate the user interface of a WinRT and a Windows Phone project (iOS and Android will be explained later)


Translation in the PCL

The PCL has no user interface, so we need to worry only about how to get translated strings from code. Resource files automatically generate a strongly typed class with all the resources (see the associated file AppResources.Designer.cs file). We can use this class to get access to the strings:

string label = AppResources.users;


Translation in Windows Phone

Remember, from all the previous steps, that we have a ResourceService class in our PCL project to provide translated strings. We have created a static resource in the App.xaml that creates an instance of this service.

We can get translated strings in XAML by using data binding against the service. Here is an example for the string “users” created before:

<TextBlock Text="{Binding LocalizedResources.users, Source={StaticResource LocalizedStrings}}" />

Notice the use of the static resource defined in App.xaml, which we named LocalizedStrings. From code, we can use the same service, as follows:

label.Text = AppResources.users;


Translation in WinRT

We use a similar approach to the translation in Windows Phone. We have also created the static resource in App.xaml. In XAML you get a translated string as follows:

<TextBlock Text="{Binding LocalizedResources.users, Source={StaticResource LocalizedStrings}}" />

Notice the use of the static resource defined in App.xaml, which we named LocalizedStrings. From code, we can use the same service, as follows:

label.Text = AppResources.users;

There is one drawback to doing translation this way in WinRT: we don’t get to use the x:Uid way of translating UI elements. But this is not a problem since this way only applies to WinRT and we want to do translations once for all platforms.


Translation in Other Platforms

Let’s see how we are on our goals:

  • It must be based on resource files: Yes! We use resource files to declare our strings
  • Resource files should be maintained in only one project: Yes! We use the Windows Phone project to maintain the resources
  • I should be able to use the Multilingual App Toolkit tool to maintain the translation: Yes! We use the Multilingual App Toolkit from the Windows Phone project to translate our strings.
  • It should use the platform specific way of implementing internationalization: Yes for Windows Phone and WinRT, no yet for iOS and Android.

iOS and Android do not use resource files, they use their own method of declaring internationalization strings. So far our solutions doesn’t work for these two platforms.

There is a very good article by Chris Miller in Code Magazine called Cross-Platform Localization for Mobile Apps where the author provides a very good explanation of how internationalization should be done in iOS and Android.

Resources in Android

In Android, resources are specified in XML files, and are contained in folder structure inside the Resources folder, where each folder is specified as Values-<languageCode>:

Android resources

The contents of a resource file is as follows:

Android resources XML

Resources in iOS

In iOS, resources are specified as text files, and are contained in a folder structure in the root of the project, where each folder is specified as <languageCode>.lproj:

iOS resources

Each folder has a Localizable.strings file containing the following structure:

iOS resources strings


A Partial Solution

In the Code Magazine article, the author solves the gap between resource files and the iOS/Android method using a very clever approach based on T4 templates. The author provides the T4 templates in the article, but I thought these could be improved. The ones provided by the article require some manual modifications before you can use them. Also, in the case of iOS you have to use string literals in order to reference the strings, and we don’t like to use string literals, we would rather use an auto-generated class with all the strings as constants. So I think this is a partial solution to the problem.

As I did in the case of the resources files for windows, I thought I could give a better approach to the one provided in Code Magazine. So, here we go.


An Improved T4 Template to Generate iOS/Android String Resources

For this we will need a couple of extensions from the Visual Studio gallery. We need the T4 Toolbox, and optionally if we want to edit the T4 templates, a T4 editor such as the Tangible T4 Editor. Once the extensions are installed, restart Visual Studio and reopen your solution.

We will create the T4 templates in our PCL project. In the PCL project, under the Resources folder, add a new T4 template (select a “Text Template” item from the “Add New Item…” dialog box in VS). Call the template Resx2OthersTemplate.tt, and replace its content with the following:

<#@ include file="T4Toolbox.tt" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ Assembly Name="System.Windows.Forms" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#+ 

public static class ResxUtilities
{
    public static Dictionary<string, string> GenerateResources(string resourceName)
    {
        Dictionary<string, string> items = new Dictionary<string, string>();
        string locale = GetLocale(resourceName);

        if (locale != "")
        {
            locale = "_" + locale;
        }

        // Read in the .resx file and collect the data elements
        if (File.Exists(resourceName))
        {
            XDocument document = XDocument.Parse(File.ReadAllText(resourceName));

            foreach(var item in document.Element("root").Elements("data")) 
            { 
                string Name = EscapeName(item);
                string Value = EscapeValue(item);

                items.Add(Name, Value);
            }
        }

        return items;
    }

    public static string GetNameSpace(string filename)
    {
        string [] words = filename.Replace(".\\", "").Split(new char[] {'.'});
        return words[0];
    }

    public static string GetLocale(string filename)
    {
        filename = Path.GetFileName(filename);
        string [] words = filename.Replace(".\\", "").Split(new char[] {'.'});

        if (words.Length > 2)
        {
            return words[1];
        }
        else
        {
            return "";
        }

    }

    public static string EscapeName(XElement item)
    {
        string name = item.Attribute("name").Value;
        return Regex.Replace(name, "[^a-zA-Z0-9_]{1,1}", "_");
    }

    public static string EscapeValue(XElement item)
    {
        XElement vitem = item.Descendants().FirstOrDefault();
        string name = vitem.Value;
        name = name.Replace("'", "\\'");
        return name;
    }

    public static string GetLanguage(string filename)
    {
        string lang = null;
        var f = Path.GetFileName(filename);
        var foo = f.Split('.');
    
        if (foo.Count() > 2)
        {
            if (foo[1].Length == 2)
            {
                lang = foo[1];
            }
        }
        if (foo.Count() == 2)
        {
            lang = "en";
        }
    
        return lang;
    }
}

public class Resx2AndroidTemplate : Template
{
    public string ResxFileName {get; set;}

    public override string TransformText()
    {
        string fullname = this.Context.Host.ResolvePath(ResxFileName);
        Dictionary<string, string> items = ResxUtilities.GenerateResources(fullname);

        int l = items.Count;

        WriteLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        WriteLine("<resources>");

        foreach(KeyValuePair<string, string> pair in items)
        {
            Write("  <string name=\"");
            Write(pair.Key);
            Write("\">");
            Write(pair.Value);
            WriteLine("</string>");
        }
        WriteLine("</resources>");

        return this.GenerationEnvironment.ToString();
    }
}

public class Resx2iOSTemplate : Template
{
    public string ResxFileName {get; set;}

    public override string TransformText()
    {
        string fullname = this.Context.Host.ResolvePath(ResxFileName);
        Dictionary<string, string> items = ResxUtilities.GenerateResources(fullname);

        int l = items.Count;

        foreach(KeyValuePair<string, string> pair in items)
        {
            WriteLine(String.Format("\"{0}\"=\"{1}\";", pair.Key, pair.Value));
        }

        return this.GenerationEnvironment.ToString();
    }    
}

public class Resx2ClassTemplate : CSharpTemplate
{
    public string ResxFileName {get; set;}

    public override string TransformText()
    {
        #>
namespace <#= DefaultNamespace #>
{
    public static class Strings
    {
<#+
        string fullname = this.Context.Host.ResolvePath(ResxFileName);
        Dictionary<string, string> items = ResxUtilities.GenerateResources(fullname);

        foreach(KeyValuePair<string, string> pair in items)
        {
#>
        public static string <#= Identifier(pair.Key) #> = "<#= pair.Key #>";
<#+
        }
#>
    }
}
<#+
        return this.GenerationEnvironment.ToString();
    }    
}
#>

In the same folder add another T4 template and call it Resx2Others.tt, and replace its content with the following:

<#@ template debug="true" hostSpecific="true" #>
<#@ output extension="log" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.IO" #>
<#@ assembly name="EnvDTE" #>
<#@ include file="Resx2OthersTemplate.tt" #>
<#
    // Create instances of the templates for iOS and Android
    Resx2AndroidTemplate androidTemplate = null;
    Resx2iOSTemplate iosTemplate = null;
    Resx2ClassTemplate classTemplate = new Resx2ClassTemplate();

    var hostServiceProvider = (IServiceProvider)Host;
    var dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));        

    foreach(EnvDTE.Project project in dte.Solution.Projects)
    {
        string projectName = project.Name.ToLower();
        if(projectName.Contains("ios") || projectName.Contains("touch"))
        {
            iosTemplate = new Resx2iOSTemplate();
            iosTemplate.Output.Project = project.FullName;
            iosTemplate.Output.ItemType = "Content";
        }
        else if(projectName.Contains("droid"))
        {
            androidTemplate = new Resx2AndroidTemplate();
            androidTemplate.Output.Project = project.FullName;
            androidTemplate.Output.ItemType = "AndroidResource";
        }
        
    }
    
    // Set the current directory to the .tt folder
    Directory.SetCurrentDirectory(Path.GetDirectoryName(Host.TemplateFile));

    // Set the file mask for the resx files to read from
    var files = Directory.GetFiles(".", "AppResources*.resx");

    foreach(var resxFile in files)
    {
        WriteLine("Processing file {0}", resxFile);

        // Fix up the file name
        string resxFileName = resxFile.Replace(".\\","");
                
        string locale = ResxUtilities.GetLocale(resxFile);

        if (!(locale.Equals("qps-ploc", StringComparison.CurrentCultureIgnoreCase)))
        {
            if (!string.IsNullOrWhiteSpace(locale))
            {
                locale = "-" + locale.Replace("-", "-r");
            }

            // Android
            if(androidTemplate != null)
            {
                androidTemplate.ResxFileName = resxFileName;
                string androidStringsFolder = @"Resources\Values" + locale;

                // Set the destination filename and path and transform the resource
                androidTemplate.Output.File = Path.Combine(androidStringsFolder, Path.GetFileName(Path.ChangeExtension(ResxUtilities.GetNameSpace(resxFile), ".xml")));
                androidTemplate.Output.Encoding = Encoding.UTF8;
                androidTemplate.Render();
            }

            // iOS
            if(iosTemplate != null)
            {
                iosTemplate.ResxFileName = resxFileName;
                // Don't need the locale, just the language
                var lang = ResxUtilities.GetLanguage(iosTemplate.ResxFileName);

                if (lang != null) 
                {
                    iosTemplate.Output.File = Path.Combine(lang + ".lproj",  "Localizable.strings");
                    iosTemplate.Output.Encoding = Encoding.UTF8;
                    iosTemplate.Render();
                }
            }

            // generate a class file with constants only for the main resource file (which doesn't specify a locale)
            if(String.IsNullOrWhiteSpace(locale))
            {
                classTemplate.Output.File = "Strings.cs";
                classTemplate.ResxFileName = resxFileName;
                classTemplate.Render();
            }
        }
    }
#>

This is the main T4 template, the one that needs to run. Notice that this template includes the Resx2OthersTemplate.tt template. These templates are just a rewrite of the templates found in the Code Magazine article, so the credit should go to Chris Miller for coming out with this solution. All I did was to improve the idea, adding some automations to remove constants and also to create a strong typed class with all the string names to be used in code. This is a summary of the improvements I made:

  • The template automatically detects if a iOS and/or Android project is part of the solution. If an iOS/Android is not part of the solution then it skips the creation of strings for that platform.
  • The template automatically finds out the name of the projects, so there is no need to specify these in the template
  • The template automatically generates a Strings.cs file in the PCL project providing constants to be used instead of string literals in the code.
  • The template sets the appropriate content type for iOS to “Content” and for Android to “AndroidResource”
  • The code was changed to be more modular

T4 templates do not run automatically on build (unless you have the pro version of the Tangible T4 editor). There are some solutions in the internet to have T4 templates run on build, but I will not mention them here since this article is already too long. For now, remember to run the T4 template (right click on Resx2Others.tt and choose from the menu the option “Run Custom Tool”) every time you modify your translations. Of course you don’t need to do this on every build, I guess that the way you translate an app is to start with one (e.g. Windows Phone) and once you have all the app translated, you use the Multilingual App Toolkit to translate strings, and then you run the T4 template to update the translation strings for both iOS and Android.


Translation in Android

When you run the T4 template you will automatically generate the Values-<languageCode> folders and the AppResources.xml files with all the strings. To translate elements of the UI you will use the following:

<TextView
    android:text="@string/users"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:id="@+id/textView1" />

In code you will use the following:

label.Text = Resources.GetString(Resource.String.users);


Translation in iOS

When you run the T4 template you will automatically generate the <languageCode>.lproj folders and the Localizable.strings files with all the strings. To translate in code you will use:

label.Text = NSBundle.MainBundle.LocalizedString(Strings.users, Strings.users);

Notice that we are using a class named Strings instead of the string literals. This class was generated as part of the T4 template and is included in the PCL project. This class is generated as follows:

public static class Strings
{
    public static string ResourceFlowDirection = "ResourceFlowDirection";
    public static string ResourceLanguage = "ResourceLanguage";
    public static string ApplicationTitle = "ApplicationTitle";
    public static string AppBarButtonText = "AppBarButtonText";
    public static string AppBarMenuItemText = "AppBarMenuItemText";
    public static string users = "users";
}

With this auto-generated class we avoid the use of string literals in our projects.


Sample App

You can find a sample VS project here:


Conclusion

Let’s review again our goals:

  • It must be based on resource files: Yes! We use resource files to declare our strings
  • Resource files should be maintained in only one project: Yes! We use the Windows Phone project to maintain the resources
  • I should be able to use the Multilingual App Toolkit tool to maintain the translation: Yes! We use the Multilingual App Toolkit from the Windows Phone project to translate our strings.
  • It should use the platform specific way of implementing internationalization: Yes! for Windows Phone, WinRT, iOS and Android.

I think that this approach is a much better one than the one used in MvvmCross. Let me know what you think, leave a comment!

1 Comments

There are many options to manage data in Windows RT and Windows Phone. You can store data locally on the device using XML o JSON files, you can store data on the Cloud using Azure Mobile Services, you can use local databases, etc. Each one of these solutions have their advantages and disadvantages. In this article I will focus on the use of local databases, specifically using the SQLite database. You can find more information about SQLite on their web site www.sqlite.com. The good thing about SQLite is that it works on almost any platform you can think of, including of course Windows RT and Windows Phone. If you want to effectively use PCL and have most of the logic of the app shared between Windows RT and Windows Phone, then your best option (at least, at the time I write this article) is to use SQLite.

sqliteSQLite has SDK packages for both Windows RT (8.0 and 8.1) and Windows Phone (8) that can be installed as a .vsix package into Visual Studio. These are just wrappers to the sqlite library (written in C++) and their syntax is not what we would expect if our project is using C#/XAML. Besides, both SDKs are not suitable for a PCL project and we can’t have all the data access logic in our PCL project. Nick Randolph’s blog  has a very good 4-article series on how to use SQLite in a PCL library. He basically sets four goals: the project has to be blendable, has to use SQLite, all the data access logic needs to be in a PCL project, and the data access needs to be done using objects (LINQ style). I highly recommend reading the articles since it gives a step-by-step guide on how to install SQLite SDKs, and a bunch of nuget packages to achieve the goal. You will see that it is not easy to have it all configured, and having to do that every time you start a new project might be cumbersome. But, hey! there was no other alternative at the time he wrote the article, so kudos to him.

Fortunately, there is now a nuget package that will make things easier for you: SQLite.Net PCL. This is actually a set of packages you need to install (depending on the platform) in order to use SQLite. The thing is that setting it up is not trivial, you need to take the information from a bunch of blog posts, github docs, and even some source files, and there are some gotchas you need to be aware of. In this article I will summarize my experience in setting things up, hopefully to save time to those who would like to use the package. This is a step-by-step guide, so grab a cup of coffee and be ready to spend some time reading since it is a long article.

As with Nick’s articles, we will have the same 4 goals in mind (blendable, SQLite, PCL and LINQ). Of course, if we want to effectively achieve these goals we need to architecture our projects to use MVVM. I’m a big fan of MVVM Light, and this is what I will use in this guide.

Project structureLet’s start by creating a project structure such as the one we have in the figure. We have three projects, and the suffix for the project name describes it, but lets explain it in detail:

  • SampleApp.PCL: this is where all the logic for the app, including the data access layer, will be developed.
  • SampleApp.WinRT: this is the WinRT (in this case Windows 8.1) application. Notice that we have included a reference to the SampleApp.PCL project
  • SampleApp.WP: this is the Windows Phone (in this case, Windows Phone 8) application. Notice that we have included a reference to the SampleApp.PCL project

Notice the project structure is already organized to work with MVVM (notice the folder structure) and services and view models are already wired up with the IoC container. You can create your own project structure, or you can follow the structure I described in this set of articles:

The last article presents a sample application that uses device isolated storage for the data access layer. I will use the code in that article to change the data layer to SQLite. You can download the sample project from here.

Project configuration: the platform

Before continuing you need to change the platform of the project configuration. By default, the platform is AnyCPU. If you leave as this you will get errors when trying to install the nuget packages (and later, to run the project). This is because the SQLite’s SDKs are not built for AnyCPU, they must target a specific platform. The platform you choose should be based on the following:

  • For Windows Phone:
    • ARM: if you run on the device and to package the app for the Windows Store
    • x86: to debug and run on the emulator
  • For WinRT
    • x86: you can use this to debug and run on the simulator. You also need this to package the app for the Windows Store
    • x64: to package the app for the Windows Store

In the case of WinRT, when submitting the app in the Windows Store, you need to upload both x86 and x64 versions of your app.

For now we will use x86 on both the WinRT and WP projects. On VS, right click the solution and choose properties. On the configuration properties node change the platform from AnyCPU to x86 for both the WinRT and WP project:

platformConfiguration

SQLite.Net nuget package on the PCL project

SQLite.Net is actually a set of nuget packages. There is a common package for the PCL project, and then there is a package for each of the platforms you want to target. Let’s us start with our PCL project. On the PCL project, using the nuget package manager, install both SQLite.Net PCL (SQLite.Net-PCL) and SQLite.Net.Async PCL (SQLite.Net.Async-PCL) packages.

SQLite.Net nuget package on the WinRT project

For the WinRT project we need to install the same packages, plus the device specific package. The device specific package is the SQLite.Net PCL - WinRT Platform (SQLite.Net.Platform.WinRT). This package depends on a package called SQLite for Windows Runtime (Windows 8.1) – Redistribution (SQLite.WinRT.redist), which installs the SQLite SDK for the WinRT platform. The problem with this package (at the time of this writing) is that it targets a version of SQLite that is not the current one (when I write this article the latest SQLite SDK is version 3.8.4.3, and the version used by the nuget package is 3.8.4.1), so if you have already installed SQLite SDKs you will get an error when installing the nuget package. To workaround this, first uninstall the version of the SQLite SDK for the WinRT platform, and then add the nuget package.

If you have a version of the SQLite SDK that is equal or lower than the one targeted by the SQLite.WinRT.redist then you’re fine. If your installed version is greater then follow these steps (again, only do this if your version of the SQLite SDK is greater than the one targeted by SQLite.WinRT.redist):

  • Open Visual Studio, go to “Tools” menu, and then select “Extensions and Updates…”.
  • On the dialog box, on the tree that is displayed on the left, select “Installed –> SDK”.
  • Locate the extension called “SQLite for Windows Runtime (Windows 8.1)” and click Uninstall.
  • Restart Visual Studio

Now you’re ready to install the nuget packages. On the WinRT project, install the SQLite.Net.Platform.WinRT package. This will attempt to install first SQLite.WinRT.redist and you will notice that a .vsix package for the SQLite SDK is installed. Unfortunately the package will fail to install the first time, I guess because VS and the nuget package manager are not aware of the newly installed SDK. The workaround is to restart VS and then attempt to install the nuget package again. This time it should install without any problems.

Now, if you’re like me, who likes to use the latest version whenever possible, you would like to use the latest version of the SQLite SDK. These are the steps you need to use the latest version:

  • Uninstall the version of the SQLite SDK that was installed with the SQLite.WinRT.redist package using the steps outlined above. Close VS.
  • Download the latest .vsix package from the SQLite download page. At the time of this writing is version 8.3.4.3. You will probably download a .zip file, that’s ok, just change the file extension to .vsix. Install this package.
  • Now reopen your solution in VS. On the WinRT project expand the References node. You will see that the reference to the SQLite SDK has a warning: SQLite reference warning
  • Remove this reference.
  • Add a reference to the new SQLite SDK. You will find this reference on the node for Windows –>Extensions SQLite WinRT reference
  • Compile to make sure everything works

winrtReferencesOf course, if you do this, you need to repeat all the above every time you start a new project that uses SQLite. I whish that the SQLite.Net package would not depend on the SQLite.WinRT.redist, because this whole process is a pain in the a..., but anyway… let’s move on.

The SQLite.Net.Platform.WinRT will also install the base SQLite.Net-PCL package. For consistency, let’s add the SQLite.Net.Async-PCL as well. The figure shows how the WinRT project references should be at this point.


SQLite.Net nuget package on the WP project

If you haven’t done so before, install the SQLite SDK for the Windows Phone platform first. Download the latest .vsix package from the SQLite download page. At the time of this writing is version 8.3.4.3. You will probably download a .zip file, that’s ok, just change the file extension to .vsix and then install this package. Make sure you restart VS.

wpReferencesNow, on your WP project use the nuget package manager to install the the SQL.Net PCL – WindowsPhone8 Platform (SQLite.Net.Platform.WindowsPhone8) package.  The SQLite.Net.Platform.WindowsPhone8 will also install the base SQLite.Net-PCL package. For consistency, let’s add the SQLite.Net.Async-PCL as well.

Next, add a reference to the SQLite SDK. On the References node, choose “Add Reference..” and on the “Windows Phone –> Extensions” node select the SQLite SDK SQLite WP reference

The figure shows how the WP project references should be at this point.

Compile to make sure everything works fine.


Using SQLite

Now for the fun part, let’s change or data access to use SQLite. If you’ve followed along with my project we will make changes to our existing model and configure a new data access to be injected in our IoC container. If you’re not following along with my project, that’s ok, just create the files with the full code below.

The model

Let’s add an id property to our model. This id will be our primary key for the object. On the PCL project, on the “Models” folder, open the file Person.cs and make the following changes:

using GalaSoft.MvvmLight;
using SQLite.Net.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Models
{
    public class Person : ObservableObject
    {
        private int id;
        [PrimaryKey, AutoIncrement]
        public int Id 
        {
            get { return id; }
            set { Set(ref id, value); }
        }

        private string name;
        public string Name
        {
            get { return name; }
            set { Set(ref name, value); }
        }
    }
}

Notice the use of the PrimaryKey and AutoIncrement attributes (which are self explanatory).

The storage service

SQLite is just a file in the device storage. We will save this file on the application isolation storage. We need to tell the SQLite API the location of this file on the device. The location of a file on the device is dependent of the device, so this needs to be handled by a service. In our project we already have a service that handles storage specific actions. On the PCL project, on the “Services\Interfaces” folder, open the IStorageService.cs file and add the highlighted code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public interface IStorageService
    {
        Task SaveTextAsync(string file, string text);

        Task<string> LoadTextAsync(string file);

        Task<string> GetPathForFileAsync(string file);
    }
}

The GetPathForFileAsync method will provide SQLite with the full path to the database file. To implement this new method go to the WinRT project and on the “Services” folder open the file StorageService.cs and add the method implementation as follows:

public async Task<string> GetPathForFileAsync(string file)
{
    StorageFile storageFile = await ApplicationData.Current.LocalFolder.CreateFileAsync(file, CreationCollisionOption.OpenIfExists);

    return storageFile.Path;
}

Notice the use of StorageFile API to create the file, and the CreationCollisionOption.OpenIfExists option used. For SQLite to work the file needs to exist. If the file doesn’t exists you will get an error when trying to use the SQLite API. This is one of the gotchas to be aware of. Notice also that the file is created on the application local folder.

The data service

We will create our data service for SQLite now. On the PCL project, on the “Services” folder, add a new class and name it SQLiteDataService.cs:

using SampleApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SQLite.Net;
using SQLite.Net.Interop;
using SQLite.Net.Async;
using System.IO;

namespace SampleApp.Services
{
    public class SQLiteDataService : IDataService
    {

        private IStorageService storageService;
        private ISQLitePlatform sqlitePlatform;

        private SQLiteAsyncConnection connection;

        public SQLiteDataService(ISQLitePlatform sqlitePlatform, IStorageService storageService)
        {
            this.sqlitePlatform = sqlitePlatform;
            this.storageService = storageService;

            InitializeAsync();
        }

        private async Task InitializeAsync()
        {
            if (connection == null)
            {
                string databaseFile = await storageService.GetPathForFileAsync("database.sqlite");
                connection = new SQLiteAsyncConnection(() => new SQLiteConnectionWithLock(sqlitePlatform, new SQLiteConnectionString(databaseFile, false)));
                await connection.CreateTableAsync<Person>();
            }
        }

        public async Task<IEnumerable<Person>> GetPersonsAsync()
        {
            return await connection.Table<Person>().ToListAsync();
        }

        public async Task AddPersonAsync(Person person)
        {
            await connection.InsertAsync(person);
        }
    }
}

There are a couple of things to notice here. Remember that we are using the PCL version of the SQLite.Net package. This PCL version actually relies on the platform specific implementation of SQLite. All PCL methods are based on the ISQLitePlatform interface. Each platform implements this interface and handles the platform's specific details for SQLite. In our case we are injecting the specific platform implementation using our IoC container. Notice how the constructor expects a ISQLitePlatform object which is assigned to a local property (lines 18 and 24).

The other thing to notice is the initialization of the database. First we get a reference to the file using our storage service (line 34) and then a SQLiteAsyncConnection is instantiated by passing the ISQLitePlatform implementation and the full path to the database file (line 35). I’m using the async implementation of the API, but you can use the synchronous one as well. The key thing is the platform specific object passed on the constructor.

The other parts of the file don’t deserve a details explanation since they just use the SQLite API (for more info refer to the SQLite docs or to the SQLite.Net docs).

We need to tell the IoC container to use this new data service. On the PCL project, on the “Services” folder, open the LocatorService.cs file and make the changes highlighted below:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using SampleApp.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.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.SQLiteDataService>();
            }

            // View Models
            SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
        }

        public IMainViewModel MainViewModel
        {
            get
            {
                return ServiceLocator.Current.GetInstance<IMainViewModel>();
            }
        }

        public static void Cleanup()
        {
        }
    }
}

Also, we need to register with the IoC container the platform specific implementation of the ISQLitePlatform interface on both the WinRT and WP projects. On the WinRT project, on the “Services” folder, open the LocatorService.cs file and make the changes highlighted below:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using SQLite.Net.Interop;
#if NETFX_CORE
using SQLite.Net.Platform.WinRT;
#else
using SQLite.Net.Platform.WindowsPhone8;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public class DeviceLocatorService
    {
        static DeviceLocatorService()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            if (ViewModelBase.IsInDesignModeStatic)
            {
            }
            else
            {
            }

            #if NETFX_CORE
            if (!SimpleIoc.Default.IsRegistered<ISQLitePlatform>()) SimpleIoc.Default.Register<ISQLitePlatform, SQLitePlatformWinRT>();
            #else
            if (!SimpleIoc.Default.IsRegistered<ISQLitePlatform>()) SimpleIoc.Default.Register<ISQLitePlatform, SQLitePlatformWP8>();
            #endif

            if (!SimpleIoc.Default.IsRegistered<IStorageService>()) SimpleIoc.Default.Register<IStorageService, StorageService>();
        }

        public static void Cleanup()
        {
        }
    }
}

Notice the use of compiler directives, which is another trick to share files between WP and WinRT projects. In our case the LocatorService.cs file is a shared file (the WP project as a link to it from the WinRT project). We register the appropriate ISQLitePlatform interface: on WinRT we use SQLitePlatformWinRT and on WP we use SQLitePlatformWP8. With this, the IoC container will inject the appropriate SQLite implementation in the PCL data service.

Compile and make sure everything works fine. You should now be able to add objects to your SQLite database on both WinRT and WP, and all the data access logic developed on the PCL project. You can see the contents of your SQLite database using a tool such as sqlitebrowser.

You can download the sample project for this article from here:

1 Comments

In the article How to prepare a cross platform visual studio project for MVVM, I explained how to create a Visual Studio solution for cross platform application development using PCL and MVVM. In this article I continue with the sample project and implement a very simple example to put things together and see how the project structure and organization is used.

Let’s do a quick example. Let’s do a list of persons and display it on each devices using our recently configured MVVM project.

The model

On the PCL project, on the “Models” folder, create a file called Person.cs and replace it with this code:

using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Models
{
    public class Person : ObservableObject
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { Set(ref name, value); }
        }
    }
}

We are inheriting from ObservableObject (from MVVM Light) thus implementing INotifyPropertyChanged. We will now create a simple service to store data in the device. For this we need two services: a data service that will be common to all platforms, and a storage service that will be device specific.

The data service

Let’s start with the storage service. The interface for this service is declared in the PCL, but the implementation will be specific to both WinRT and WP. On the PCL project, on the “Services\Interfaces” folder, create a class called IStorageService.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public interface IStorageService
    {
        Task SaveTextAsync(string file, string text);

        Task<string> LoadTextAsync(string file);
    }
}

Now, let’s implement this interface on each of the devices. On the WinRT project, on the “Services” folder create a class named StorageService.cs:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Storage;

namespace SampleApp.Services
{
    public class StorageService : IStorageService
    {

        public async Task SaveTextAsync(string fileName, string text)
        {
            StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

            byte[] data = Encoding.UTF8.GetBytes(text);

            using (var s = await file.OpenStreamForWriteAsync())
            {
                await s.WriteAsync(data, 0, data.Length);
            }
        }

        public async Task<string> LoadTextAsync(string fileName)
        {
            try
            {
                StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);

                using (var s = await file.OpenStreamForReadAsync())
                {
                    using (var sr = new StreamReader(s))
                    {
                        return await sr.ReadToEndAsync();
                    }
                }
            }
            catch (Exception)
            {
                Debug.WriteLine("Error loading file '{0}'", fileName);
                return null;
            }
        }
    }
}

This is pretty simple, we just create a file on the application folder with the content. Let’s do the same for the WP project, but instead of creating and repeating the code, we will use the same file for the WinRT project, since the code is basically the same and it works without any changes on WP. This will be more and more the case as Microsoft is slowly converging both platforms, and now with the announce in Build2014 of universal apps this convergence is much closer. On the WP project, on the “Services” folder, add a link to the StorageService.cs file created before.

Now, we need to register both services with the IoC container. We will do this in LocatorService.cs file on the WinRT project (if you remember from the previous article, we just added this same file as a link on the WP project, se we need to change it once). Add the highlighted lines to the file:

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 SampleApp.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()
        {
        }
    }
}

Compile your solution to make sure everything works. Now, let’s create the data service. This data service will use the storage service to store the data in an XML format. In the PCL project, on the “Services\Interfaces” folder, create a file called IDataService.cs and replace the code with this:

using SampleApp.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public interface IDataService
    {
        Task<IEnumerable<Person>> GetPersonsAsync();

        Task AddPersonAsync(Person person);
    }
}

As I mentioned before, let’s keep it simple :-). Now the implementation, in the PCL project, on the “Services” folder, create a file called DataService.cs with this code:

using SampleApp.Models;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;

namespace SampleApp.Services
{
    public class DataService : IDataService
    {
        private const string dataFile = "data.xml";

        private IStorageService storageService;
        private List<Person> persons;

        public DataService(IStorageService storageService)
        {
            this.storageService = storageService;
        }

        public async Task<IEnumerable<Person>> GetPersonsAsync()
        {
            if (persons == null) await LoadAsync();
            return persons;
        }

        public async Task AddPersonAsync(Person person)
        {
            if (persons == null) await LoadAsync();
            persons.Add(person);
            await SaveAsync();
        }

        private async Task SaveAsync()
        {
            StringWriter sw = new StringWriter();
            XmlSerializer serializer = new XmlSerializer(typeof(List<Person>));
            serializer.Serialize(sw, persons);

            await storageService.SaveTextAsync(dataFile, sw.ToString());
        }

        private async Task LoadAsync()
        {
            string data = await storageService.LoadTextAsync(dataFile);

            if (!string.IsNullOrEmpty(data))
            {
                StringReader sr = new StringReader(data);
                XmlSerializer serializer = new XmlSerializer(typeof(List<Person>));

                persons = (List<Person>)serializer.Deserialize(sr);
                
            }
            else
            {
                persons = new List<Person>();
            }
        }
    }
}

The service just serializes and deserializes data to and from an XML file. Notice the constructor of the service, it expects as a parameter an instance to a storage service, this instance is resolved by the IoC container and injected at run time. The service that will be injected will be the one registered in the locator service specific to the platform where the app is running. Everything is wired up appropriately.

Design time data

Now, this works fine when running on the device, but what about Blend? What about design time data? Designers will thank us if we provide design time data as part of the project as well. Let’s do this. On the PCL project, create a folder called “Design” and add a class named DataService.cs:

using SampleApp.Models;
using SampleApp.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Design
{
    public class DataService : IDataService
    {
        public Task<IEnumerable<Person>> GetPersonsAsync()
        {
            List<Person> persons = new List<Person>() {
                new Person() { Name = "Giovanni" },
                new Person() { Name = "Antonio"}
            };

            TaskCompletionSource<IEnumerable<Person>> tcs = new TaskCompletionSource<IEnumerable<Person>>();
            tcs.SetResult(persons);
            return tcs.Task;
        }

        public Task AddPersonAsync(Person person)
        {
            throw new NotImplementedException();
        }
    }
}

The above class will provide design time data for our views. Notice that we only provide the implementation of the GetPersosnAsync method, since this is the only method that we will need at design time. Now, we need to register this service to be used instead of the previous one when we are working at design time. We do this in the LocatorService.cs file in the PCL project. Open the file and add the lines highlighted below:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using SampleApp.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.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()
        {
        }
    }
}

With this the IoC container will inject the design time data service when in design time, and the real data service (the one that works with the device storage) at run time on the device.

We have completed the model and the data access, next thing is the view model.

The view model

Let’s add some methods to our view model. In the PCL project, on the “ViewModels\Interfaces” folder, edit the IMainViewModel.cs file as follows:

using GalaSoft.MvvmLight.Command;
using SampleApp.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace SampleApp.ViewModels
{
    public interface IMainViewModel
    {
        ObservableCollection<Person> Persons { get; }

        RelayCommand RefreshCommand { get; }
        RelayCommand AddCommand { get; }
    }
}
We’ve added an observable collection to bind to the views (we will do this later) and also commands to

execute business logic. Let’s implement the interface, open the MainViewModel.cs file and replace it with the following code:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using SampleApp.Models;
using SampleApp.Services;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace SampleApp.ViewModels
{
    public class MainViewModel : ViewModelBase,IMainViewModel
    {
        private IDataService dataService;

        public MainViewModel(IDataService dataService)
        {         
            this.dataService = dataService;

            RefreshAsync();

        }

        private ObservableCollection<Person> persons = new ObservableCollection<Person>();
        public ObservableCollection<Person> Persons
        {
            get
            {
                return persons;
            }
        }

        #region Commands

        #region Refresh
        private RelayCommand refreshCommand;
        public RelayCommand RefreshCommand
        {
            get
            {
                return refreshCommand
                    ?? (refreshCommand = new RelayCommand(
                                          async () =>
                                          {
                                              await RefreshAsync();
                                          }));
            }
        }

        private async Task RefreshAsync()
        {
            persons.Clear();
            foreach (Person person in await dataService.GetPersonsAsync())
                persons.Add(person);
        }
        #endregion

        #region Add
        private RelayCommand addCommand;
        public RelayCommand AddCommand
        {
            get
            {
                return addCommand
                    ?? (addCommand = new RelayCommand(
                                          async () =>
                                          {
                                              Person person = new Person() { Name = "Giovanni" };
                                              await dataService.AddPersonAsync(person);
                                              persons.Add(person);
                                          }));
            }
        }
        #endregion

        #endregion
    }
}

Notice how the constructor expects a reference to a data service, which, as explained before, will be injected by the IoC container. The other thing to notice is the use of the design time data, the constructor does a refresh of the data so we can have data when working with the views.

We have completed our view model. Now we will work the views.

The views

I will provide a very simple view, no beauty on it, we will leave this for the designer. Instead, let’s focus on the code. Let’s first work the WinRT view. We will add a grid view and an app bar to add and refresh the data. On the WinRT project, open the MainPage.xaml and replace it with the following:

<Page
    x:Class="SampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <GridView ItemsSource="{Binding Persons}">
            <GridView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <TextBlock Text="{Binding Name}" />
                    </Grid>
                </DataTemplate>
            </GridView.ItemTemplate>
        </GridView>
    </Grid>

    <Page.BottomAppBar>
        <AppBar>
            <Grid>
                <StackPanel Orientation="Horizontal">
                    <AppBarButton Command="{Binding AddCommand}" Icon="AddFriend" Label="Add Person" />
                </StackPanel>

                <StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
                    <AppBarButton Command="{Binding RefreshCommand}" Icon="Refresh" Label="Refresh" />
                </StackPanel>
            </Grid>
        </AppBar>
    </Page.BottomAppBar>
</Page>

We have used data binding to bind the list of persons and the commands as well. No code-behind! If you do this in the VS XAML editor, you will see the design time data:

WinRT view

On WP, we have one problem: the app bar doesn’t provide binding to commands. For this we will rely on a cool package called AppBarUtils. On the WP project, add this package using the nuget package manager. Replace the code in MainPage.xaml with the following:

<phone:PhoneApplicationPage
    x:Class="SampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"    
    xmlns:abu="clr-namespace:AppBarUtils;assembly=AppBarUtils"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    DataContext="{Binding MainViewModel,Source={StaticResource Locator}}">
    
    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
            <TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <phone:LongListSelector Margin="0,0,-22,0" ItemsSource="{Binding Persons}">
                <phone:LongListSelector.ItemTemplate>
                    <DataTemplate>
                        <Grid>
                            <TextBlock Text="{Binding Name}" />
                        </Grid>
                    </DataTemplate>
                </phone:LongListSelector.ItemTemplate>
            </phone:LongListSelector>
        </Grid>

        <!--<Image Source="/Assets/AlignmentGrid.png" VerticalAlignment="Top" Height="800" Width="480" Margin="0,-32,0,0" Grid.Row="0" Grid.RowSpan="2" IsHitTestVisible="False" />-->
    </Grid>

    <phone:PhoneApplicationPage.ApplicationBar>
        <shell:ApplicationBar Mode="Default" Opacity="1.0" IsMenuEnabled="True" IsVisible="True">
            <shell:ApplicationBarIconButton IconUri="/Assets/AppBar/add.png" Text="add" />
            <shell:ApplicationBarIconButton IconUri="/Assets/AppBar/refresh.png" Text="refresh" />
        </shell:ApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

    <i:Interaction.Behaviors>
        <abu:AppBarItemCommand Id="add" Command="{Binding AddCommand}" Text="Add Person" />
        <abu:AppBarItemCommand Id="refresh" Command="{Binding RefreshCommand}" Text="Refresh" />
    </i:Interaction.Behaviors>
</phone:PhoneApplicationPage>

We have used data binding to bind the list of persons and the commands as well. Again, no code-behind! If you do this in the VS XAML editor, you will see the design time data:

WP

We have completed the views. Now, test it (pray) and make sure that everything works.

You can download the sample code from here:

2 Comments

In a previous blog post I explained how to configure a visual studio project structure for cross platform application development, using a PCL project for most of our app logic. In this article I will continue on the example and explain how to prepare the projects to work with MVVM. There is a lot of documentation about MVVM on the web so I won’t repeat it here, instead I will focus on project organization: where do I put models? how do I organize folders? what changes do I need to make in VS?, etc. I hope with this article to save you time reading a bunch of articles that only show part of the big picture.

visualStudioSolutionI’m assuming you have a project structure such as the one in the figure, where you have a PCL project for the business logic (our models and view models in MVVM jargon), and a project for each of the platforms you want to target: Windows RT and Windows Phone in our example. The PCL project has been included as a reference on each of the platform specific projects.

We want to do things right so we will use an IoC container to inject objects in our code. You can use any IoC container you want, I’m a big fan of MVVM Light, so this is what I will use for the example. MVVM Light has its own IoC container and also has some utility classes that are very useful for MVVM. I recommend you read the documentation and see some sample of MVVM Light on its web page.

MVVM has three main entities: models, view models and views. Where do we put these? Models and view models should be independent of the platform, so they should go in the PCL project. Views, on the other hand, are dependent of the platform (e.g. to present a list of items in WinRT we use a  GridView, while in Windows Phone we use a LongListSelector), so they should go on each of the platform projects. There is another entity in a good MVVM project: services.

Services fill the gap between the platform independent code in the PCL and the platform dependent code on each of the platform projects. This is better illustrated with an example: suppose our business logic (which is coded in the view model) needs to display a message to the user; messages are displayed differently on each platform, so a dialog box in WP looks different and has a different API than the one in WinRT. The view model doesn’t know (and this is ok) how to display a message for a specific platform. This is the whole purpose of MVVM, each layer is only concerned with what it needs to do. Displaying a message is the job of the platform. So, how can we display a message to the user if the logic is in the view model. This is where services come in handy. We will have a service for each platform and we will tell the view model to use that service. When the view model wants to display a message to the user, it will call the appropriate service to do it. Since view models are independent of the platform, services are injected at run time using an IoC container. This works the same for a bunch of other features such as accessing devices specific hardware (GPS, accelerometer, etc.), accessing data on the device, working with the device screen, etc. There’s a lot of documentation on this on the web, this was just a brief description since we will need to know about this fourth entity (the service) when creating our project structure.

The views

Since this is all about organization, let’s start by organizing our views. When we create the WinRT/WP project VS will automatically create a MainPage.xaml (a view) into our project root. If we follow this, we will end up with all our views in the root folder. I like my root folder very clean, so the first thing is to create a “Views” folder on each of the WinRT and WP folder and move MainPage.xaml into it. If you just do this, you will find that your project might not even start. There are some things you need to do as well.

In the WinRT project this is straightforward, create the “Views” folder and move the MainPage.xaml into it. That’s it! This works since the initial page to show is called from App.xaml.cs on the OnLaunched event, and since we haven’t changed the name of the class nor its namespace things still work. In WP project is not as easy. Create the “Views” folder and move the MainPage.xaml into it, open the app manifest (located in Properties\WMAppManifest.xml) and edit the “Navigation Page” field to specify the full path to the page, including the “Views” folder (see image below). You will have a project structure as follows:

Views folder created on projects


The services

servicesNow, in the PCL project create folders for the “Models”, “ViewModels” and “Services”, as shown in the figure. For the “Models” and “ViewModels” folder, create a subfolder called "Interfaces”. This folder is where we will create all the interface declarations. Create a “Services” folder on both the WinRT and WP project as well. You should now have a structure such as the one on the figure.

Next, use the nuget package manager to install MVVM Light PCL. Install only the libraries. You need to install this package on all three projects. Next we will configure the MVVM Light IoC container and wire things up. We need a locator service, which will be responsible for configuring the IoC container and to provide the right view models to the view. In the case of platform services we will need to configure these on both WinRT and WP projects.

On the PCL project, on the “Services” folder, create a file called LocatorService.cs. Replace the code with the one below:

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 SampleApp.Services
{
    public class LocatorService
    {
        static LocatorService()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);


            // Services
            if (ViewModelBase.IsInDesignModeStatic)
            {
            }
            else
            {
            }

            // View Models
        }


        public static void Cleanup()
        {
        }
    }
}

We will later add code to this file to register all the services and view models. For the moment let’s leave it as it is and compile the solution. We will register here services that are cross platform. For services that are platform specific (most of them) we will need a similar file on both the WinRT and WP projects. On the WinRT project, on the “Services” folder, create a file called LocatorService.cs and replace the code with the following:

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 SampleApp.Services
{
    public class DeviceLocatorService
    {
        static DeviceLocatorService()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            if (ViewModelBase.IsInDesignModeStatic)
            {
            }
            else
            {

            }

        }

        public static void Cleanup()
        {
        }
    }
}

Notice that the name of the class is DeviceLocatorService. This is different than the one specified on the PCL project, since this locator service is for device specific services and since they both share the same namespace (remember that in our case we want the same namespace for all projects) we need to specify a different name. We need to do this on the WP project as well, but instead of creating a new class we will add a link to the file used for the WinRT project, as the code is the same for both platforms. This is one of the tricks used to share code between platform projects, just make sure to select the “Add as Link” from the drop down button in the “Add existing item” dialog box.

Now we need to register these services on each of the platforms. On the WinRT project, open the file App.xaml, add the lines highlighted below:

<Application
    x:Class="SampleApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:ignore="http://www.ignore.com"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"    
    mc:Ignorable="d ignore"    
    xmlns:services="using:SampleApp.Services"    
    xmlns:local="using:SampleApp">

    <Application.Resources>
        <ResourceDictionary>
            <services:DeviceLocatorService x:Key="Locator.W8" d:IsDataSource="True" />
            <services:LocatorService x:Key="Locator" d:IsDataSource="True" />
        </ResourceDictionary>
    </Application.Resources>
</Application>

The important thing to understand is that by declaring both classes (DeviceLocatorService and LocatorService) as resources the application will automatically execute the static code on both classes, thus configuring the IoC container with the classes we need. Notice that the device locator service is specified before the generic locator service. This is because the generic locator service will need references to device services and thus is dependent of these.

On the WP project, open the App.xaml file and add the lines highlighted below:

<Application
    x:Class="SampleApp.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    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:SampleApp.Services;assembly=SampleApp.PCL"
    xmlns:deviceServices="clr-namespace:SampleApp.Services">

    <!--Application Resources-->
    <Application.Resources>
        <local:LocalizedStrings xmlns:local="clr-namespace:SampleApp" x:Key="LocalizedStrings"/>
        <deviceServices:DeviceLocatorService x:Key="Locator.WP8" d:IsDataSource="true" />
        <services:LocatorService x:Key="Locator" d:IsDataSource="true" />
   </Application.Resources>

    <Application.ApplicationLifetimeObjects>
        <!--Required object that handles lifetime events for the application-->
        <shell:PhoneApplicationService
            Launching="Application_Launching" Closing="Application_Closing"
            Activated="Application_Activated" Deactivated="Application_Deactivated"/>
    </Application.ApplicationLifetimeObjects>

</Application>

At this point, compile your projects and make sure everything runs.

The view models

We will now create our view model for the MainPage.xaml view. On the PCL project, on the ViewModels\Interfaces folder, create a file called IMainViewModel.cs, and replace the code with the one below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.ViewModels
{
    public interface IMainViewModel
    {        
    }
}

VS by default will add the folder structure as part of the namespace. This means that the interface will have the namespace SampleApp.ViewModels.Interfaces. This is ok, but I don’t want that many namespaces so I remove the .Interfaces part and make it just one namespace for the view models SampleApp.ViewModels. Next, let’s create the implementation of this interface. In the PCL project, on the “ViewModels” folder, create a file called MainViewModel.cs and replace the code as follows:

using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;


namespace SampleApp.ViewModels
{
    public class MainViewModel : IMainViewModel
    {
    }
}

Now, let’s register this view model with the IoC container and wire it up on the view. On the PCL project, open the LocatorService.cs file and add the lines highlighted below:

using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using SampleApp.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace SampleApp.Services
{
    public class LocatorService
    {
        static LocatorService()
        {
            ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);

            // Services
            if (ViewModelBase.IsInDesignModeStatic)
            {
            }
            else
            {
            }

            // View Models
            SimpleIoc.Default.Register<IMainViewModel, MainViewModel>();
        }

        public IMainViewModel MainViewModel
        {
            get
            {
                return ServiceLocator.Current.GetInstance<IMainViewModel>();
            }
        }

        public static void Cleanup()
        {
        }
    }
}

On the WinRT project, open the Views\MainPage.xaml file and add the lines highlighted below:

<Page
    x:Class="SampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SampleApp"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    DataContext="{Binding MainViewModel, Source={StaticResource Locator}}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    </Grid>
</Page>

We are setting up the data context of the page to the view model, which is exposed by the service locator (identified by the key Locator) through the property MainViewModel. We will do the same for our WP project. Open the Views\MainPage.xaml and add the lines highlighted below:

<phone:PhoneApplicationPage
    x:Class="SampleApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True"
    DataContext="{Binding MainViewModel,Source={StaticResource Locator}}">
    
    <!-- code omitted for simplicity -->

</phone:PhoneApplicationPage>

The project structure is already set up, things are wired up, and we can now start working on the specifics of our application.

I the next article I will explain how to use this project structure for a sample application.