XAM250 Patterns for Cross Platform Mobile Development

Exercise 1: Use the Factory Pattern to access a dependency from shared code (XAM250)

This exercise will take an existing iOS, Android, or Windows project and pull out sharable code into a Portable Class Library (PCL), using the Factory Pattern to isolate the platform-specific code to read and write the storage file used to display quotes.

Screenshot of the completed exercise.
Completed exercise
To complete the exercise, you will need Visual Studio for Windows or macOS with the Xamarin development tools installed. You will also need either an emulator/simulator or a device to run the exercise on. Please see the setup page if you need help installing the Xamarin development environment.

Open the starter solution

Open the starter solution from the Exercise 1 > Start folder in your copy of the cloned or downloaded course materials in either Visual Studio on Windows or Visual Studio for Mac.


Create the Portable Class Library

Your first step is to create the library to hold our shared code - you'll use a Portable Class Library (PCL) (vs. a Shared Project) as it will force you to put more thought into how you architect your shared code and provide a distinct boundary between the projects.

  1. Add a new PCL project to the solution. Name it GreatQuotes.Data.
  2. Remove the blank source file added to the project by default.
  3. Move the GreatQuote class from the Data folder in your platform project into the PCL. Make sure it's no longer in the platform-specific project and is only in the PCL project.
  4. Add a reference to the PCL to your platform project so it has access to the model data.
  5. Build the solution and make sure it still works - all you've done is move a file which was already portable so it should.
namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerMemberNameAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerFilePathAttribute : Attribute
    {
    }

    [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
    public class CallerLineNumberAttribute : Attribute
    {
    }
}
Depending on the supported targets you have selected for the Portable Class Library, it's possible that some of the attributes used in the GreateQuote.cs file are not supported. In particular, the [CallerMemberName] attribute might be missing. There are two ways to fix this problem. * First, you can change the supported target frameworks and remove support for Silveright 5 which does not have this attribute. * Second, you can add the following code into your project. The [CallerMemberName] feature doesn't have a runtime component, just having the specific named attribute is enough to get the compiler to support the feature. Copy the following block and paste it into a new C# source file in your project and you should be able to compile.

Create the abstraction for the Quote Loader

Next, you'll need to provide an abstraction for the Quote Loader code you're using. If you try to add it to the PCL directly, it will fail to compile because it has dependencies against specific file APIs which are not available in your current profile. In addition, each platform is slightly different in how it handles local files and where they are placed. As a result, you need a unique approach to load our quotes for each platform.

  1. Open the existing QuoteLoader.cs file in your platform-specific project (iOS, Android or Windows) - this file is located in the Data folder of each project. Examine the methods and implementation presented here.
    • Load is used to load quotes from a file.
    • Save is used to save an existing collection of quotes back to the same file.
  2. Create a new interface to represent our QuoteLoader independent of the platform. You'll use an interface here, but you could also use an abstract base class as well. The interface should be placed into the Portable Class Library as it will be shared across all our projects.

    • Name it IQuoteLoader.
    • Add definitions for the Load and Save methods just as they are in the in your platform project.
    public interface IQuoteLoader
    {
        IEnumerable<GreatQuote> Load();
        void Save(IEnumerable<GreatQuote> quotes);
    }
    
  3. Have your platform-specific implementation of QuoteLoader implement this interface - you shouldn't need to make any code changes to it as the signatures for Load and Save should already be present in the existing class.

    public class QuoteLoader : IQuoteLoader
    
  4. Build and run the application and make sure it still works properly.

Create the Factory to create the IQuoteLoader

Next, you'll utilize the Factory pattern to create the property implementation of the IQuoteLoader that you'll use in our PCL code.

  1. Create a new static class named QuoteLoaderFactory in the Portable Class Library. This will represent the factory class you'll use to create your platform-specific implementation of an IQuoteLoader.
  2. Add a single static property named Create that is of type Func<IQuoteLoader>. This property is what you'll set in order to create a new IQuoteLoader.
  3. Here is what your code should look like for the factory:
public static class QuoteLoaderFactory
{
   // This must be assigned to a method which creates a new quote loader.
   public static Func<IQuoteLoader> Create { get; set; }
}

Refactor the code

In all three platform-specific projects, there is almost identical code to load and save the quotes. This currently utilizes the QuoteLoader class directly, however you'd like to push this common code into your shared code (the PCL). In particular, you want to move the management of the GreatQuote collection into shared code so that every platform locates the data the same way. Start by creating a class to manage your quotes.

  1. Create a new QuoteManager class in the PCL.
  2. Use the Singleton Pattern to create a static Instance property to expose a single copy of the QuoteManager. You can use the built-in Lazy<T> type to implement this pattern, or just create the object the first time the property is accessed; the goal is to have a public, static property to get to a single, known instance of the object. If you need some help, you can use the below, simple example of creating a singleton in C#. You can also look at the Completed.V1 lab which uses the more efficient Lazy<T> approach.

    public class QuoteManager
    {
       private static readonly QuoteManager instance = new QuoteManager();
    
       public static QuoteManager Instance { get { return instance; } }
    
       private QuoteManager()
       {
          ...
       }
    }
    
  3. Add a public IList<GreatQuote> property named Quotes to expose the loaded quotes.
  4. In the constructor, assign the property to a new ObservableCollection<GreatQuote> instance.
  5. Next, obtain an IQuoteLoader object using the QuoteLoaderFactory.Create delegate and assign it to a field of your class. To do this, just call the delegate assigned to the property:

    IQuoteLoader loader;
    ...
    private QuoteManager()
    {
       ...
       loader = QuoteLoaderFactory.Create();
    }
    
  6. Then, populate your list of quotes using the Load method from the IQuoteLoader field, this returns an IEnumerable<GreatQuote>.
  7. Finally, add a new public, instance method named Save which saves the collection of quotes using the quote Loader field's Save method.

Use the Quote Manager and Assign the Factory

The final step is to use the new shared QuoteManager class and assign the factory.

  1. Open the application level class which was loading the quotes - this is AppDelegate in AppDelegate.cs for iOS, App in App.cs for Android, and App in App.xaml.cs for Windows.
  2. Next, assign the QuoteLoaderFactory.Create property of your factory class to a method which creates a new platform-specific QuoteLoader class (e.g. something that implements IQuoteLoader).
    • You can use any delegate assignment style you prefer - lambda, anonymous method, or a regular C# method defined in your class.
    • Add this code into the FinishedLaunching override in the iOS project.
    • Add this code into the OnCreate override in the Android project.
    • Add this code into the App constructor in the Windows project.
  3. Next, remove the Quotes collection and the quote loader code from the platform-specific code. Locate the static List<GreatQuote> that is used when loading and saving quotes. Here's what the line looks like:

    public static List<GreatQuote> Quotes { get; private set; }
    
  4. This is the code you'd like to remove, remember that each project has it in a different file:

    • iOS - AppDelegate.cs
    • Android - App.cs
    • Windows - App.xaml.cs
  5. Try to compile your app - this will produce several errors because of our refactoring.
    • Go through each error where the Quotes collection was being referenced and fix each spot to now use the common QuoteManager.Instance.Quotes property. This will reduce the coupling in the current code by using a more formalized pattern to locate the quotes.
    • Change the call to the original QuoteLoader.Save method to use the new QuoteManager.Instance.Save method. This is in the same application-level class as the loading code. You should get a compile error which will point you at the correct spot to fix.
  6. Build and run the app to verify it loads and saves quotes properly. Try putting a breakpoint into the QuoteManager class where it obtains the quote loader - trace through it to see it jump from the cross-platform (shared) code into each platform specific project.
  7. If you have time, update the other platform-specific project to use your new Portable Class Library and QuoteManager.

Exercise summary

In this exercise, you've taken an existing set of applications and moved the data management code into a Portable Class Library (PCL), utilizing the Factory Pattern to load and save the data to a file.

You can view the completed solution in the Exercise 1 > Completed folder of your copy of the cloned or downloaded course materials.

Go Back