Shared Libraries For Windows Phone 7, MonoDroid and Beyond

If you’ve been doing mobile development, you probably know all about the problem of having to target multiple platforms, along with the languages each one requires. Having to rewrite and support essentially the same code on different platforms flat out sucks, and isn’t really a great way to spend your time. Thanks to the fine folks working on Mono and its satellite projects, it’s possible to leverage the power of the .NET framework across many platforms. In this article I’ll focus on sharing the same code across Android and Windows Phone 7, but you’ll easily see how it could be extended to Silverlight, ASP.NET or iOS without much effort at all.

These days it seems like the ubiquitous mobile app example is some sort of Twitter client, so why should I be any different? We’ll build a very simple app that let’s you search Twitter for a given term. First, let’s set up the projects we’re going to need in a new solution. This post assumes that you’re using a non-express edition of Visual Studio 2010 since that’s what MonoDroid requires.

Setup

First, make sure you have the Project Linker extension installed in Visual Studio. This extension makes it really easy to share code between projects without having to manually copy code over and maintain changes. You can find it by just searching in Visual Studio’s Extension Manager (Tools -> Extension Manager). Once that’s installed, set up the following projects in a blank solution:

  1. TwitterSearcher: a normal .NET class library
  2. TwitterSearcher.MonoDroid: a MonoDroid class library
  3. TwitterSearcher.WP7: a Windows Phone class library
  4. App.MonoDroid: a MonoDroid application
  5. App.WP7: a Windows Phone 7 Silverlight application

Now for TwitterSearcher.MonoDroid and TwitterSearcher.WP7, right click on the projects and select “Add project link”. In the dialog that pops up, select the TwitterSearcher project. This will set it up so that when you add any source files to TwitterSearcher (the source project), they will automatically be added to the destination projects. Since they all share the same physical file, changes are shared automatically. When you add a project link it will not automatically pick up files that are already in the source project, so a quick workaround to that is to exclude them from the project, and then re-include them.

For App.MonoDroid and App.WP7, add references to TwitterSearcher.MonoDroid and TwitterSearcher.WP7, respectively. Also add a reference to System.Xml.Linq for TwitterSearcher.WP7.

Why Is This Necessary?

You might be wondering why we need to go through all this effort to share code. After all, isn’t everything running on .NET in the end? The short answer is that each platform compiles its assemblies against a different platform. For these projects, Windows Phone 7 will compile against Silverlight, whereas MonoDroid will compile against the Mono libraries it includes. You can generally use the same source code across them (we’ll talk about how to handle exceptions later), but they should be compiled against the correct platform.

In some cases you might be able to get away with adding references to assemblies and projects compiled for other platforms, but you can run into issues, particularly with debugging. When dealing with your own code like we are here, it’s best to just target the right platform to make sure things work like you’d expect.

Building Out TwitterSearcher

I wasn’t lying when I said this would be a very simple app. We’ll set up a small POCO for holding the data in a tweet, and then set up a class that handles searching. First, here’s the model:

public class Tweet
{
    public DateTime Published { get; set; }
    public string Title { get; set; }
    public string Author { get; set; }
}

Here’s the class we’ll use to search Twitter:

public class Searcher
{
    private string _baseSearchUrl;
    private static XNamespace _namespace = "http://www.w3.org/2005/Atom";

    public Searcher(string baseUrl)
    {
        _baseSearchUrl = baseUrl;
    }

    public void Search(string query, Action<IEnumerable<Tweet>> callback)
    {
        var searchClient = new WebClient();

        searchClient.DownloadStringCompleted += (sender, e) =>
        {
            IEnumerable<Tweet> results =
                XElement.Parse(e.Result)
                    .Descendants(_namespace + "entry")
                    .Select(entry => new Tweet()
                    {
                        Title = (string)entry.Element(_namespace + "title"),
                        Published = DateTime.Parse((string)entry.Element(_namespace + "published")),
                        Author = (string)entry
                                            .Descendants(_namespace + "author")
                                            .First()
                                            .Element(_namespace + "name")
                    });

            callback(results);
        };
        searchClient.DownloadStringAsync(new Uri(_baseSearchUrl + Uri.EscapeDataString(query)));
    }
}

There we harness the power of LINQ To XML to make it easy to parse Twitter’s search results. In the interest of full disclosure, I will say that one reason I went for the asynchronous request was because I knew that Silverlight does not let you make synchronous requests, which you’ll find out at runtime when the app blows up. Keeping things asynchronous is a good practice anyway, since it helps make sure that your long running operations don’t tie up the UI thread in the app, allowing it to stay responsive.

That’s all of our shared code. It’s very simple, but you can see that the core functionality of the apps (searching Twitter) is encapsulated in the same code. You should now be able to compile the solution and have it build all the versions of the library, along with the empty applications that reference them.

The Android App

First we’ll build the Android version of the app. You can delete any default activities and layouts added by the project. Under Resources/layout, add an XML file named main.xml that defines our main application layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/query"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1.0"
            android:lines="1"
            android:imeOptions="actionGo"
            android:selectAllOnFocus="true" />
        <Button
            android:id="@+id/search_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Search" />
    </LinearLayout>
    <ListView
        android:id="@+id/results"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />
</LinearLayout>

It’s just a simple text box, a search button, and a list of results. In that same layout folder, also create tweet_item.xml that declares what a simple search result will look like in the list:

<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10sp">
    <TextView
        android:id="@+id/tweet_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="5sp" />
    <TextView
        android:id="@+id/tweet_author"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
    <TextView
        android:id="@+id/tweet_published"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />
</LinearLayout>

Remember that for the resources to be compiled correctly, the Build Action for both of them should be set to AndroidResource. Next we need to define a list adapter that will translate a collection of Tweets into a view for the list.

public class TweetAdapter : BaseAdapter
{
    private Activity _context;
    private IEnumerable<Tweet> _tweets;

    public TweetAdapter(Activity context, IEnumerable<Tweet> tweets)
    {
        _context = context;
        _tweets = tweets;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var view = (convertView
                        ?? _context.LayoutInflater.Inflate(
                                Resource.layout.tweet_item, parent, false)
                    ) as LinearLayout;
        var tweet = _tweets.ElementAt(position);

        view.FindViewById<TextView>(Resource.id.tweet_text).Text = tweet.Title;
        view.FindViewById<TextView>(Resource.id.tweet_author).Text = tweet.Author;
        view.FindViewById<TextView>(Resource.id.tweet_published).Text = tweet.Published.ToString("f");

        return view;
    }

    // ...some code omitted, see sample project...

I left out a couple functions that are required for the class but aren’t interesting for this post, but you can see the full source in the sample solution at the end. This code simple grabs a Tweet from the results and inflates a view (using the layout in tweet_item.xml) using the values in the Tweet.

Last but not least, we need an Activity that runs everything:

[Activity(Label = "Twitter Search", MainLauncher = true)]
public class SearchActivity : Activity
{
    private Searcher _searcher;
    private ListView _resultsList;
    private TextView _queryText;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView(Resource.layout.main);

        _searcher = new Searcher("http://search.twitter.com/search.atom?q=");

        _resultsList = FindViewById<ListView>(Resource.id.results);
        _queryText = FindViewById<TextView>(Resource.id.query);

        FindViewById<Button>(Resource.id.search_button).Click += delegate
        {
            var progressDialog = ProgressDialog.Show(this, "Searching", "Please wait...", true);

            _searcher.Search(_queryText.Text.ToString(), results =>
            {
                RunOnUiThread(delegate
                {
                    _resultsList.Adapter = new TweetAdapter(this, results);

                    progressDialog.Hide();
                });
            });
        };
    }
}

It sets up a Searcher object, and uses it to find results when the button is clicked. While waiting for the results to come back, we show a progress dialog so the user knows something is happening. Now if you fire up the application, you should be able to run some searches, assuming you have an internet connection of course.

The Windows Phone 7 App

Now that we have a working Android app, let’s build out the WP7 version. The Android version didn’t have much code, and this version will have even less. First we’ll need to define a value converter to format the date/time of a tweet properly.

public class TweetPublishedValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((DateTime)value).ToString("f");
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        DateTime converted;

        if (DateTime.TryParse(value.ToString(), out converted))
        {
            return converted;
        }

        return DependencyProperty.UnsetValue;
    }
}

All that does is it takes a DateTime and formats it how we want. You can tweak that to format however you’d like. Next we’ll write up the XAML for the UI, in MainPage.xaml. I will only include the main content section of the page here, but you can see it in context in the sample project.

<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <ProgressBar IsIndeterminate="True" Name="ProgressBar" Visibility="Collapsed" />

    <TextBox Height="72" HorizontalAlignment="Left" Margin="25,20,0,0" Name="Query" VerticalAlignment="Top" Width="275" />
    <Button Content="Search" Height="72" HorizontalAlignment="Left" Margin="308,20,0,0" Name="Search" VerticalAlignment="Top" Width="129" Click="Search_Click" />
    <ListBox Height="503" HorizontalAlignment="Left" Margin="25,98,0,0" Name="Results" VerticalAlignment="Top" Width="412">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Width="370" Margin="0,10,0,10">
                    <TextBlock Text="{Binding Title}" TextWrapping="Wrap" FontSize="24" Margin="0,0,5,0" />
                    <TextBlock Text="{Binding Author}" Foreground="#FFC8AB14" FontSize="20" />
                    <TextBlock Text="{Binding Path=Published, Converter={StaticResource PublishedConverter}}" Foreground="#FFC8AB14" FontSize="18" />
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
</Grid>

That defines the same basic layout that we had in the Android version: a text box, a button and a list of results. There’s also a progress bar that we’ll show and hide when appropriate. Now all that’s left is some modification to MainPage.xaml.cs. To keep things simple for this post I am putting the logic right into the code-behind. In a real app, you would want to use the MVVM pattern to help get this logic out of the code-behind as much as possible, but that’s outside the scope of what I want to cover. Our code-behind will look like this:

public partial class MainPage : PhoneApplicationPage
{
    private Searcher _searcher;

    public MainPage()
    {
        InitializeComponent();
    }

    private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
    {
        _searcher = new Searcher("http://search.twitter.com/search.atom?q=");
    }

    private void Search_Click(object sender, RoutedEventArgs e)
    {
        ProgressBar.Visibility = System.Windows.Visibility.Visible;
        Results.Visibility = System.Windows.Visibility.Collapsed;

        _searcher.Search(Query.Text, results =>
        {
            Results.ItemsSource = results;

            ProgressBar.Visibility = System.Windows.Visibility.Collapsed;
            Results.Visibility = System.Windows.Visibility.Visible;
        });
    }
}

The logic is much like the Android version, and the experience should be pretty much the same as well if you fire it up. Now we have the same Twitter core library code running on two platforms. Pretty cool, right?

What About Platform-Specific Code?

You might have been wondering what happens when you put code into the source project that isn’t compatible with the profile of one of the destination projects (for example, a call to WebClient.DownloadString exists in the standard .NET framework, but not in Silverlight). What happens then? As you might expect, it just won’t compile. Though not particularly elegant, you can use preprocessor directives to include/exclude code depending on the platform. By default, a Windows Phone 7 app will compile with two symbols you can look for: SILVERLIGHT and WINDOWS_PHONE. You can see these by right clicking on the project and going to Properties, and then going to the Build tab. MonoDroid doesn’t have any set by default, but you can easily add one if you want to check use it.

To use it in your code, it would look something like:

#if WINDOWS_PHONE
    // this code only compiles for Windows Phone
#elif MONODROID
    // this code only compiles for MonoDroid
#endif

Like I said before, it can get a little messy but it does work. Depending in what you’re doing, there might be better options for abstracting out code differences but this can come in handy for some situations.

Summary

Being able to share business logic and write what are essentially UI clients on each platform is a very powerful thing, and keeps you from repeating yourself. With very little effort you could extend it further to have a Silverlight version, a MonoTouch version, or you can use the base .NET version in ASP.NET, WPF or WinForms applications. With each platform you end up with a native application that looks and feels like it is supposed to. Often people ask for a one-size-fits-all solution that can just be run on all platforms, but what you usually end up with is an app that doesn’t feel right on any of them.

You can download the full solution containing all the projects here.

Tags: , , ,

23 Responses to “Shared Libraries For Windows Phone 7, MonoDroid and Beyond”

  1. [...] here to read the rest: Shared Libraries For Windows Phone 7, MonoDroid and Beyond « Greg … If you enjoyed this article, please consider sharing it! Tagged with: [...]

  2. Shared Libraries For Windows Phone 7, MonoDroid and Beyond…

    Thank you for submitting this cool story – Trackback from DotNetShoutout…

  3. It would be much better if we have some way to take the XAML content and try tranlating it into android xml files!

  4. A translation tool could be cool, but I think it would probably also lead to the problem I mentioned of developers using the same UI on different platforms, which often doesn’t feel right. I think redoing the UI based on platform is a small price to pay for getting the native look and feel of that platform.

  5. Общие библиотеки для Windows Phone 7, MonoDroid и так далее…

    Thank you for submitting this cool story – Trackback from progg.ru…

  6. [...] Shared Libraries For Windows Phone 7, MonoDroid and Beyond [...]

  7. Nice article. We are using the excellent Novell Mono tools and a process similar to what you describe to perform cross-platform .NET development on Linux, Mac, iOS and Android.

    However, we ran into one serious issue. MonoTools for Visual Studio (the Linux/Mac tool) cannot debug line-by-line with linked project files. This prevents us from easily sharing code files across platform projects, forcing us to manually copy and sync files, a messy process indeed.

    We hope that Novell soon fixes this bug, as reported here: https://bugzilla.novell.com/show_bug.cgi?id=655144

  8. Oh that’s interesting, I didn’t know that. MonoDroid currently has a bug that prevents you from debugging in class libraries at all, so I hadn’t had the chance to hit the one you mentioned. Definitely hope they both get fixed soon!

  9. Hi

    Using preprocessor is considered to be not so clean. It is possible to use ProjectLinker feature and extract/separate platform specific code to source code file with different extensions and ProjectLinker does not link them to other projects. For example *.Silverligh.cs *.WPF.cs.

    Currently we have managed to target 4 platforms and no idefs

    Best regards

    moljac

  10. That’s a pretty nice solution to the problem, thanks for the comment! I agree that directives aren’t clean (which I mentioned in the article), but wanted to at least include them for completeness since they are still used often.

  11. Hi Greg,
    definitely the way to go. The project linker tip is golden :-) The issue I still struggle is how to fire an event back on the UI thread from an asynchronous operation in a non-UI component on Android, similar to what Dispatcher.BeginInvoke does in Silverlight and BeginInvokeOnMainThread in MonoTouch.

    I guess it is somehow related to Application.ApplicationContext.MainLooper, but this whole concept of Loopers and Handlers is totally awkward.

  12. Nevermind, I found a solution I think:
    var looper = Application.ApplicationContext.MainLooper;
    var h = new Handler(looper);
    h.Post(a);

    Btw. I made a nice little graphic to explain the concept to potential customers:
    http://www.winphoneapps.de/monotouch.html

    Can’t thank Miguel and team enough, this is the smartest way to develop cross platform IMO.

  13. Hi

    The project linker looks like a very useful plugin. However if I were to target Monotouch in this way I wouldn’t be able to use it since you can only develop with Monotouch using Monodevelop in a Mac environment. Considering this does Monodevelop have an equivalent to the VS Project Linker plugin or is there some other technique that is used to achieve the same thing?

    Thanks
    Liam

  14. You don’t actually need the extension to do linking since it’s built right into Visual Studio out of the box. The extension just makes it a bit simpler by managing changes in linked projects for you (adding/removing files, etc). I know that MonoDevelop also supports linking, so the same strategy should work on that side as well.

  15. Hi
    I am trying to implement this setup using SQLite in the C# project. When I try to run the application I get a build error saying that it doesn’t have the reference to the assembly. Even if I reference the Mono SQLite dll I still get an error saying the Mono assembly doesn’t exist.
    Do you know of a good way to implement the data access code inside the shared project with SQLite? I understand that Windows phone would have a different assembly to that of MonoDroid/Monotouch so is there something like those preprocessor directives be used in for assembly references?

    Liam

  16. Hi Greg,

    We are creating a application in MonoDroid trying to link Utralite to MonoDroid. But while packing the application to the emulator.

    It gives an error:

    1. Could not sign the Android package. See exception for more details

    Can you help us out with this?

    Thanks,
    Aditi Paliwal

  17. In the dialog there should be a little question mark – if you click that you should be able to see the full exception to get a better idea of why it’s failing.

    Without seeing that I can’t really help too much, but to take a wild stab at it…are you linking the source and building it like I explained in the post, or are you referencing a DLL that’s already compiled for another platform? If it’s the latter, I’m guessing that’s your problem.

  18. Hi Greg…

    A newbie question.
    When i tried to compile, a file named strings.xml is missing from the App.MonoDroid/Resources/values
    Is this file constructed in the build process or am I missing something?

    Regards
    John

  19. Do you have the latest version of the project? I took a look and I don’t see any reference to strings.xml in the project file: https://github.com/gshackles/Sample-Projects/blob/master/MonoDroid/TwitterSearcher/App.MonoDroid/App.MonoDroid.csproj

    You can also try doing a clean/rebuild to make sure you’re doing a full fresh build of the projects. If that still doesn’t work for you, let me know!

  20. What’s the reason for creating the TwitterSearcher.MonoDroid library ? Why not just link the TwitterSeacher lib directly to the App.MonoDroid ?

  21. You certainly could do that, but I generally try to keep parity across different projects to make it more maintainable. TwitterSearcher.MonoDroid’s job is to stay in sync with the main TwitterSearcher library, and can also potentially be used in other applications as well, which is the main goal of a reusable class library in the first place.

Leave a Reply