Thursday, 10 February 2011

Sharing code between two WP7 projects

So you want to publish a free (aka 'lite') as well as paid versions of your Windows Phone 7 app? Ideally you can do this with a minimum of duplication between the two projects. This is how I've approached the problem.

Create second project

Assuming you've got a solution with a working WP7 project (I'll refer to this as 'primary'). Add a new WP7 project to your existing solution (referred to as 'secondary'). In the new secondary project, add a reference to the primary project.

Move resources into resource dictionary

If you have any resources defined in your App.xaml file, you should move them to a separate xaml file and use a ResourceDictionary to reference them.
Here's a cutdown version of my Styles.xaml. This lives in the primary project and has a build action of 'Page'.
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:unsupported="clr-namespace:Microsoft.Phone.Controls.Unsupported">
    <Style x:Key="PerformanceProgressBar" TargetType="ProgressBar">
       ...
    </Style>
</ResourceDictionary>
App.xaml for primary
I've been using Caliburn Micro framework so the bootstrapper property has to be moved inside the nested ResourceDictionary.
<Application x:Class="Primary.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Primary">
    <Application.Resources>
        <ResourceDictionary >
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:AppBootstrapper x:Key="bootstrapper" />
                </ResourceDictionary>
                <ResourceDictionary Source="Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>

    </Application.Resources>
</Application>
App.xaml for secondary
In this file, we need to fully specify the path to Styles.xaml
<Application x:Class="Secondary.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Secondary">
    <Application.Resources>
        <ResourceDictionary >
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary>
                    <local:AppBootstrapper x:Key="bootstrapper" />
                </ResourceDictionary>
                <ResourceDictionary Source="/Primary;component/Styles.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>
In my case, the secondary project has it's own AppBootstrapper class that inherits from the primary project's similarly named AppBootstrapper.
There's more info on using ResourceDictionaries with WP7 in this StackOverflow question.

Make page navigation Uris fully qualified

If you have any NavigationService calls to navigate to other pages in the primary project, they will need to be fully qualified so that they continue to work when called from the secondary project.
_navigationService.Navigate( new Uri( "/Primary;component/SettingsPage.xaml", UriKind.RelativeOrAbsolute ) );

Fully qualify the value of the NavigationPage attribute in the WMAppManifest.xml file

Assuming you want the same page to load at the start in the secondary project, you'll need to change the reference in the NavigationPage attribute in the WMAppManifest.xml file.
<Tasks>
  <DefaultTask Name="_default" NavigationPage="Primary;component/MainPage.xaml" />
</Tasks>
Bear in mind that if you edit the application properties through the Visual Studio project properties interface, this will revert the NavigationPage value back to just "MainPage.xaml", so you'll need to update it again.

Add links to existing files for all 'Content' files and application bar images

imageAny files in your primary project that have a build action of 'Content' will need to be added to the secondary project. Add them as existing items and choose the 'Add as link' option so you just reference the existing file. Update the build action of all of these files to be 'Content'. Common examples of these files would include ApplicationIcon.png and Background.png.
Likewise, if you have any application bar icons, you'll probably need to do the same.

Conclusion

Following these steps produces a nice lean secondary project like this:
image
It's hard to see from the icons (as Mercurial has overlaid green ticks on top), but the only unique files to the project are App.xaml, AppBootstrapper.cs and the contents of the Properties folder. All the others are linked to ones in the Primary project.

3 comments:

Anonymous said...

Hello,

We are currently developing a WP7 application. We found this article very close to our problem. We need to share code from two projects that are in the same solution, but both projects are deployed in the device. But we need just only one to deploy.

Is there any solution for this?

Regards,
Ze Gathem

David Gardiner said...

Hi Ze,

I don't think this is possible - there is no GAC on a Windows Phone (well not one that you can deploy assemblies to).

You're going to have to deploy the same assembly twice (once for each project), or maybe use linked files to include the same source code file in your second project.

-david

Anonymous said...

Hi,
I'm currently developing a WP7/WP8 app with Caliburn Framework, and i follow your post for share main of code through two project.
I've a problem with Caliburn convenction on link View/ViewModel for data binding, for the main project there is no problema but with second can't connect ViewModel with view (i think caliburn can't navigate to correct path).
Have you the same problem? And you resolve it?