Shared Project

A Shared Project is a project that contains common code that can be linked to each platform specific library or app. It is compiled as part of the platform specific code.

To develop a nice solution based on a shared project, we need to architect it in order to have the core features in the shared project. We may possibly be using partial classes, abstract classes and interfaces in order to manage the objects that are specific for each platform in the platform specific project.

These kinds of projects are good when we are in a prototype phase, because it is relatively fast to implement a shareable project for the common code and use all the advantages given by the platform specific frameworks.

It's easy to choose a Shared Project approach over PCL when we just develop platform specific applications. A Shared Project allow us to write code to be copied to each project in the solution during compilation. In that way, we can store codes outside the platform specific project and easily translate it when we need to support more platforms. A well-architected shared project will give us more advantages than a platform specific constrained project.

Here is how we create a Shared project:

  1. Right-click on the solution and go to Add | New Project.
  2. On the left side, select Multiplatform | Library.
  3. On the right side, select Shared Project.
  4. Click on Next.

    As we can see in the following screenshot, we filled in the Project Name box and the Location box will have autocompleted:

  5. Write BusinessLayer in the empty field called Project Name.
  6. Check summary view of project.
  7. Click on Create.

In this way, we can then reference the Shared Project from the platform specific project and all the classes will be available on our platform specific project. The reference to a Shared Project doesn't import a compiled library into our platform specific project. All the files will be compiled in our platform specific project as though they were written there. For this reason, it is important to not write any platform specific code in the Shared Project. Some people do this using the conditional directives #If PLATFORM then.

To keep the code clean and maintainable, don't do it.

Tip

This statement if is often avoidable and mostly bad in software development. We usually use the if statement when we are too lazy to think and it often creates a lot of damage in our code. if can be addictive, so when we start using it we think we cannot get rid of it and we usually tend to abuse the use of statement. A less-if code makes our code more readable and maintainable. We can avoid using most of the if we write in our code using polymorphism. In this book we will use if less, so that we can also learn the concept of less-if.

Example an extension method to translate text

As we have seen in the previous chapter, every app should follow a software development life cycle. Every single example we are going to develop in this book starts with an idea and follows at least one fully.

Idea

An extension method of the type string that translates a key into a value loaded from a text file. The text file will be named after a language ISO code (like en-GB). When we ask for a translation of a file that is not available, the default text file will be the source of the translation.

Note

Extension methods enable us to "add" methods to existing types without modifying the original type. We will see in the development of this example how to write an extension method for the type string.

We can now complete our survey in order to understand if we are doing something valuable and how to proceed with the design of it:

Design

Our users are, in this case, developers. In order to make this component usable we want developers to write as little code as possible to have the maximum value from it.

Another stakeholder of our component would be the person who's going to translate the resources for us. We want them to easily translate the resources and to easily provide us the file we need in the right format.

The content of the file will be a comma separated value file:

KEY_OF_VALUE_1,Value 1 
KEY_OF_VALUE_2,Value 2 

The name of the files will be:

Default.csv 
EN-GB.csv 

The csv format is a standard format that can be edited using the most popular editors of spreadsheets. We use it so the editors can easily read and modify them, providing us with the translations.

From the developers' point of view we want the developers to be able to write something like:

string translatedValue = "KEY_OF_VALUE_1".Translate(); 

in order to have the translation of "KEY_VALUE_1" in the default language of the phone.

Development

First of all, let's open Xamarin Studio and create a new solution. Select Multiplatform | Library on the left side and Shared Project on the right side then click Next.

For the project name we can assign XamarinByExample.Chapter2.TranslatorShared; and for the solution name let's assign XamarinByExample.Chapter2; click on Create.

Let's delete the class that Xamarin creates for us called MyClass.cs. We don't need it. To delete it, right-click on the name in the Solution pad and select the Remove option. A popup will appear asking us if we want to remove it only from the project or also from the disk. In this case, we want to remove it everywhere so we will select Delete.

Now we can right click on the project XamarinByExample.Chapter2.TranslatorShared and then AddNew File.

In the Add New File dialog we can select General and the option Empty class C#. We will add Extensions as the class name and select the New button.

Before writing the code, let's prepare all the files we need. Now we need to have the default.csv file to write the default key values we want to use in our apps.

To do that, right-click on the project XamarinByExample.Chapter2.TranslatorShared and then Add | New File.

In the Add New File dialog, select General and the option Empty File. For the name, we can also include the extension, so let's write default.csv in the Name field and press New.

We can now write in the default.csv:

    HELLO_WORLD,Hello World (Translated from default)

Now let's double click on the Extension.cs in the Solution pad.

As you can see, some C# code has already been written for us by Xamarin when we added the file:

using System; namespace XamarinByExample.Chapter2.TranslatorShared
{
     public class Extensions
     {
         public Extensions ()
         {
         }
     }
 }

We are going to change it.

We need to write an extension method for the type string. Extension methods need to be written in a static class and they need to be static methods. So, the first thing we are going to add is the static statement between public and class; delete the constructor and to add a static method called Translate that returns a string; and, in order to extend the type string, have as arguments of the method a string called key with the statement this before the declaration of it. Inside the method, we are going to return an empty string for now:

public static class Extensions
     {
         /// <summary>
         /// Translate the specified key in the language dependent value.
         /// </summary>
         /// <param name="key">Key to translate.</param>
         public static string Translate(this string key)
         {
             //TODO: implement here the logic to
             the key in the value
             return "";
         }
     }

Tip

When we put a comment with TODO: Xamarin automatically creates a task for us in the Task Manager. We can find these tasks in the Tasks pad, which we open from the Tasks button at the bottom right of Xamarin Studio.

Now let's implement some logic.

First we need to load the resource file from the assembly. Then we need to read the contents of it until we find the correspondent key. When we find it, we will assign the correspondent value to return.

Let's have a look at one possible final implementation of it:

/// <summary>
/// Translate the specified key in the language dependent value.
/// </summary> 
/// <param name="key">Key to translate</param> 
public static string Translate (this string key) 
{
       //gets the assembly where this code is compiled
       var assembly = Assembly.GetExecutingAssembly(); 
 
      //extracts the namespace of the embedded resource (platform specific)  
      var ns = assembly.FullName.Split(new char[]{ ',' }, 2) [0]; 
      
       //build the name of the resource using the extracted namespace
       var resourceName = String.Format ("{0}.default.csv", ns) ;
       //define the separator of the csv file
       const char separator = ',';
       //assigns the key to the value
       var value = key;
       // using disposes the objects for us
       using (var stream = assembly.GetManifestResourceStream
       (resourceName))
       using (var reader = new StreamReader (stream)) {
         //read the file till the end and save it in a variable called result
         var result = reader.ReadToEnd ();
         //build an array where each element is a row of the file
         var lines = result.Split ('\n');
         //iterate until the value has not been replaced
         //or the file has been read till the end.
         //To put the condition inside the statement of the cycle
         //allows us to avoid writing a redundant "if" statements
         inside the cycle
         for (var i = 0; i < lines.Length && value == key; i++) {
           //separate the key from the values using the separator.  
           var keyValues = result.Split (new char[]{ separator }, 2); 
 
           // this is a ternary operator (info box)
           value = (keyValues [0] == key) ? keyValues [1] : key;
         }
       }
       //returns the value that is the same as the key
       if nothing has been found
       return value; 
} 

Note

A ternary operator is a possible syntax of a conditional expression where: If (a == b) { c } else { d }

Is written as: ( a == b ) ? c : d

We can now add a new iOS and a new Android project and reference the Shared Project in both of them to test this feature.

The only thing to remember is that to reference a Shared Project is like copying the code inside our project. It will be compiled inside the project, and not in the apparent library of the Shared Project. In fact, the Shared Project is not a library at all. It is just a container of code common to different platforms.

To test our project with the apps, we have to add the namespace where the extension method is, and then we can call everywhere we have a type string the Translate method and it will use the embedded file to find the correspondent value:

"HELLO_WORLD".Translate() 

In the original idea there was a final requirement "translate into the default language of the phone". Unfortunately, each platform has its own way to save and retrieve the default language of the phone.

We need to find a way to tell the language to the extension method from the platform specific project. The only way that the extension method has to generalize it by itself seems to be the conditional compilation statement #If PLATFORM. We do not want to do that, do we?

Note

To see one possible complete solution to this example, download it at http://www.enginpolat.com/books/xbe/XamarinByExample.Chapter2-0.zip