Cross Platform
Android
iOS
Mac
Test Cloud

Case Study: Tasky

This document describes how the principles of Building Cross Platform Applications have been applied in the Tasky Pro sample application. It touches on mobile application design, writing shared code for re-use and implementing platform-specific projects that target the iOS, Android and Windows Phone mobile platforms.

Overview

Tasky Pro is a simple to-do list application. This document discusses how it was designed and built, following the guidance in the Building Cross Platform Applications document. The discussion covers the following areas:

Design

This section describes a general approach for starting a new cross-platform mobile application project, such as generating requirements, creating screen mockups and identifying key features of the code.

Shared Code

Explains how a cross-platform database and business layer is constructed. A TaskManager class is written to provide a simple ‘API’ that is accessed by the UI Layer. The implementation details of the shared code are encapsulated by the TaskManager and the Task classes that it returns.

Platform-Specific Applications

Tasky Pro runs on iOS, Android and Windows Phone. Each platform-specific application utilizes the shared code and implements a native user-interface to do the following:

  1. Display a list of tasks
  2. Create, edit, save and delete tasks.

The platform-specific code only interacts with the shared code via the Core project’s TaskManager class. All other aspects of the data storage and business rules are encapsulated in Core project.

Design Process

Whether you do a complete design for your entire application, or just a design for your next two-week sprint/iteration, it is always advisable to create some sort of road-map for what you want to achieve before you start coding.

This is especially true for cross platform development, where you are building functionality that will be exposed in slightly different ways and will require testing on multiple platforms and devices. Having a clear idea of what the app needs to achieve saves time and effort later in the development lifecycle.

Requirements

The first step in designing an application is to identify what features you want it to have. These can be high-level goals or detailed use-cases. The Tasky Pro app has very simple goals:

  1. View a list of tasks
  2. Ability to add, edit and delete tasks
  3. Ability to set the task’s status to ‘done’

Even at this early stage it can be worthwhile considering platform capabilities and deciding whether to take advantage of them. For example, perhaps Tasky Pro can take advantage of iOS’ Twitter integration, or Windows Phone’s Live Tiles? These features may not make it into the first version, but it is still useful to plan features for the future.

User Interface Design

Start with a high-level design that can be implemented across the target platforms. If platform-specific behaviors are planned they should be considered at this stage – adding those features may affect your shared code or data model.

Draw the screen-flow on paper, using a presentation or graphics program or a specialized mock-up tool.

Data Model

Knowing what data needs to be stored will help determine what persistence mechanism to use.

The Tasky Pro app has very simple storage needs, only three properties for each ‘task’:

  1. Name – String
  2. Notes – String
  3. Done – Boolean

Core Functionality

Consider the API that the user interface will need to consume in order to meet the requirements. A to do list requires the following functions:

  1. List all tasks – to display the main screen list of all available tasks
  2. Get one task – when a task row is touched
  3. Save one task – when a task is edited
  4. Delete one task – when a task is deleted
  5. Create empty task – when a new task is created

In order to achieve code re-use, this API should be implemented once and shared across all platforms.

Implementation

Once the application design has been agreed upon, consider how it might be implemented as a cross-platform application. This will become the application’s architecture. Following the guidance in the Building Cross Platform Applications document, the application code should be broken down into the following parts:

  1. Shared Code – a common project that contains re-useable code to store the Task data; expose a Model class and an API to manage the saving and loading of data.
  2. Platform-specific Code – platform-specific projects that implement a native SDK UI for each operating system, utilizing the shared code as the ‘back end’.

These two parts are described in the following sections.

Shared (Core) Code

Tasky Pro uses the Cloned Project Files strategy for sharing common code. See the [Sharing Code Options] document for a description of how that works.

Shared code in the Core project is placed in folders relating to the architectural layer, to make it easy to navigate. The namespaces mirror the folder structure, for example Tasky.DAL.TaskRepository class is located in the DAL directory.

The complete Core project is shown below – the cloned “.csproj” project files are identical for each platform:

The class diagram below shows the classes, grouped into layers. The SQLiteConnection class is boilerplate code from the SQLite-NET github repository. The rest of the classes are custom code for Tasky Pro. The TaskManager and Task classes represent the API that is exposed to the platform-specific applications.

Using namespaces to separate the layers helps to manage references between each layer. The platform-specific projects should only need to include a using statement for the Business Layer. The Data Access Layer and Data Layer should be encapsulated by the API that is exposed by TaskManager in the Business Layer.

References

Shared code projects should generally only reference the common framework libraries, such as System, System.Core and System.Xml. This screenshot shows the iOS and Android projects only reference these assemblies:

The Windows Phone Core project includes additional references to Microsoft.Phone and Community.CsharpSqlite.WP7, which are discussed further in the Windows Phone-specific section of this document. They are required to achieve cross-platform support for the SQLite database.

When apps are compiled, the linking process will remove unreferenced code, so even though System.Xml has been referenced, it will not be included in the final application because we are not using any Xml functions in the code.

Data Layer (DL)

The Data Layer contains the code that does the physical storage of data – whether to a database, flat files or other mechanism. The Tasky Pro data layer consists of two parts: the SQLite-NET library and the custom code added to wire it up.

Tasky Pro embeds SQLite-NET code to provide a basic Object-Relational Mapping (ORM) database interface. The SQLite-NET code is available on github. The TaskDatabase class inherits from SQLiteConnection and adds the required Create, Read, Update, Delete (CRUD) methods to read and write data to SQLite. It is a simple boilerplate implementation of generic CRUD methods that could be re-used in other projects.

The TaskDatabase is a singleton, ensuring that all access occurs against the same instance. A lock is used to prevent concurrent access from multiple threads.

SQLite on WIndows Phone

While iOS and Android both ship with SQLite as part of the operating system, Windows Phone does not include a compatible database engine. In order to share code across all three platforms a Windows phone-native version of SQLite is required.

Tasky Pro uses the open source C# SQLite from Google Code to provide a SQLite implementation that matches what is built in to the other platforms. The assembly is included as a reference in the Windows Phone Core project, and compiler directives in the SQLite-NET library ensure the cross-platform calls are handled correctly.

Using an Interface to Generalize Data Access

The Data Layer takes a dependency on BL.Contracts.IBusinessIdentity so that it can implement abstract data access methods that require a primary key. Any Business Layer class that implements the interface can then be persisted in the Data Layer.

The interface just specifies an integer property to act as the primary key:

public interface IBusinessEntity {
    int ID { get; set; }
}

The base class implements the interface and adds the SQLite-NET attributes to mark it as an auto-incrementing primary key. Any class in the Business Layer that implements this base class can then be persisted in the Data Layer:

public abstract class BusinessEntityBase : IBusinessEntity {
    public BusinessEntityBase () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
}

An example of the generic methods in the Data Layer that use the interface is this GetItem<T> method:

public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return Table<T>().FirstOrDefault(x => x.ID == id);
    }
}

Locking to prevent Concurrent Access

A lock is implemented within the TaskDatabase class to prevent concurrent access to the database. This is to ensure concurrent access from different threads is serialized (otherwise a UI component might be attempting to read the database at the same time a background thread is updating it, for example). An example of how the lock is implemented is shown here:

static object locker = new object ();
public IEnumerable<T> GetItems<T> () where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return (from i in Table<T> () select i).ToList ();
    }
}
public T GetItem<T> (int id) where T : BL.Contracts.IBusinessEntity, new ()
{
    lock (locker) {
        return Table<T>().FirstOrDefault(x => x.ID == id);
    }
}

Most of the Data Layer code could be re-used in other projects. The only application-specific code in the layer is the CreateTable<Task> call in the TaskDatabase constructor.

Data Access Layer (DAL)

The TaskRepository class encapsulates the data storage mechanism behind a strongly-typed API that allows Task objects to be created, deleted, retrieved and updated.

Using Conditional Compilation

The class uses conditional compilation to set the file location - this is an example of implementing Platform Divergence (as discussed in the [Cross Platform Applications] document). The property that returns the path compiles to different code on each platform. The code and platform-specific compiler directives are shown here:

public static string DatabaseFilePath {
    get { 
        var sqliteFilename = "TaskDB.db3";
#if SILVERLIGHT
        // Windows Phone expects a local path, not absolute
        var path = sqliteFilename;
#else
#if __ANDROID__
        // Just use whatever directory SpecialFolder.Personal returns
        string libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); ;
#else
        // we need to put in /Library/ on iOS5.1 to meet Apple's iCloud terms
        // (they don't want non-user-generated data in Documents)
        string documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); // Documents folder
        string libraryPath = Path.Combine (documentsPath, "..", "Library"); // Library folder
#endif
        var path = Path.Combine (libraryPath, sqliteFilename);
                #endif
                return path;
    }
}

Depending on the platform, the output will be “<app path>/Library/TaskDB.db3” for iOS, “<app path>/Documents/TaskDB.db3” for Android or just “TaskDB.db3” for Windows Phone.

Implementing The Singleton Pattern

TaskRepository implements the Singleton pattern. It has been implemented as a singleton by:

  1. Providing only a protected constructor. The TaskRepository class (or a subclass) can create an instance of itself.
  2. Implementing a static constructor that creates an instance and populates a static field.
  3. Having methods that make calls against the static reference to perform their functions.

Because TaskRepository is a Singleton there is no initialization code required in the other application layers. When the class is first used, the Singleton is instantiated and the underlying database files are created if required.

Business Layer (BL)

The Business Layer implements the Model classes and a Façade to manage them. In Tasky Pro the Model is the Task class and TaskManager implements the Façade pattern to provide an API for managing Tasks.

Façade

TaskManager wraps DAL.TaskRepository to provide the Get, Save and Delete methods that will be referenced by the Application and UI Layers.

Business rules and logic would be placed here if required – for example any validation rules that must be satisfied before an object is saved.

API for Platform-Specific Code

Once the Core code has been written, the user interface must be built to collect and display the data exposed by it. The TaskManager class implements the Façade pattern to provide a simple API for the application code to access.

The code written in each platform-specific project will generally be tightly-coupled to the native SDK of that device, and only access the shared Core using the API defined by the TaskManager. This includes the methods it exposes and also referencing the business classes, such as Task, that it uses.

Images are not shared across platforms but added independently to each project. This is often desirable as the size/resolution required for each platform varies, even if the basic image (eg. for logos and icons) is the same. For example, each platform has a different size requirement for the ‘splash screen’: 320x480 and 640x960 for iPhone, 768x1024 and 1536x2048 for iPad, 480x800 for Windows Phone and a variety of sizes for different Android resolutions.

The remaining sections discuss the platform-specific implementation details of the Tasky Pro UI.

iOS App

There are only a handful of classes required to implement the iOS Tasky Pro application using the shared Core project to store and retrieve data. The complete iOS Xamarin.iOS project is shown below:

The classes are shown in this diagram, grouped into layers.

References

The iOS app references the platform-specific SDK libraries – eg. monotouch and MonoTouch.Dialog-1.

It must also reference the Tasky.Core.iOS shared code project. The references list is shown here:

The Application Layer and User Interface Layer are implemented in this project using these references.

Application Layer (AL)

The Application Layer contains platform-specific classes required to ‘bind’ the objects exposed by the Core to the UI. The iOS-specific application has two classes to help display tasks:

  1. EditingSource – This class is used to bind lists of tasks to the user interface. Because we’re using MonoTouch.Dialog for the Task list, we need to implement this helper to enable swipe-to-delete functionality in the UITableView. This is also an example of a platform-specific feature – swipe-to-delete is common on iOS and so Tasky Pro supports it, but not in Android or Windows Phone.
  2. TaskDialog – This class is used to bind a single task to the UI. It uses the MonoTouch.Dialog Reflection API to ‘wrap’ the Task object with a class that contains the correct attributes to allow the input screen to be correctly formatted.

The TaskDialog class uses MonoTouch.Dialog attributes to create a screen based on a class’s properties. The class looks like this:

public class TaskDialog {
    public TaskDialog (Task task)
    {
        Name = task.Name;
        Notes = task.Notes;
        Done = task.Done;
    }
    [Entry("task name")]
    public string Name { get; set; }
    [Entry("other task info")]
    public string Notes { get; set; }
    [Entry("Done")]
    public bool Done { get; set; }
    [Section ("")]
    [OnTap ("SaveTask")]    // method in HomeScreen
    [Alignment (UITextAlignment.Center)]
    public string Save;
    [Section ("")]
    [OnTap ("DeleteTask")]  // method in HomeScreen
    [Alignment (UITextAlignment.Center)]
    public string Delete;
}

Notice the OnTap attributes require a method name – these methods must exist in the class where the MonoTouch.Dialog.BindingContext is created (in this case, the HomeScreen class discussed in the next section).

User Interface Layer (UI)

The User Interface Layer consists of the following classes:

  1. AppDelegate – Contains calls to the Appearance API to style the fonts and colors used in the application. Tasky Pro is a very simple application so there are no other initialization tasks running in FinishedLaunching.
  2. ScreensUIViewController subclasses that define each screen and its behavior. Screens tie together the UI with Application Layer classes and the Core API (TaskManager). In this example the screens are created in code, however they could also have been designed using Xcode’s Interface Builder.
  3. Images – Visual elements are an important part of every application. Tasky Pro has splash screen and icon images, which for iOS must be supplied in regular and Retina resolution.

Home Screen

The Home Screen is a MonoTouch.Dialog screen that displays a list of tasks from the SQLite database. It inherits from DialogViewController and implements code to set the Root to contain a collection of Task objects for display.

The two main methods related to displaying and interacting with the task list are:

  1. PopulateTable – Uses the Business Layer’s TaskManager.GetTasks method to retrieve a collection of Task objects to display.
  2. Selected – When a row is touched, displays the task in a new screen.

Task Details Screen

Task Details is an input screen that allows tasks to be edited or deleted.

Tasky Pro uses MonoTouch.Dialog’s Reflection API to display the screen, so there is no UIViewController implementation. Instead, the HomeScreen class instantiates and displays a DialogViewController using the TaskDialog class from the Application Layer.

This screenshot shows an empty screen that demonstrates the Entry attribute setting the watermark text in the Name and Notes fields:

The functionality of the Task Details screen (such as saving or deleting a task) must be implemented in the HomeScreen class, because this is where the MonoTouch.Dialog.BindingContext is created. The following HomeScreen methods support the Task Details screen:

  1. ShowTaskDetails – Creates a MonoTouch.Dialog.BindingContext to render a screen. It creates the input screen using reflection to retrieve property names and types from the TaskDialog class. Additional information, such as the watermark text for the input boxes, is implemented with attributes on the properties.
  2. SaveTask – This method is referenced in the TaskDialog class via an OnTap attribute. It is called when Save is pressed, and uses a MonoTouch.Dialog.BindingContext to retrieve the user-entered data before saving the changes using TaskManager.
  3. DeleteTask – This method is referenced in the TaskDialog class via an OnTap attribute. It uses TaskManager to delete the data using the primary key (ID property).

Android App

The complete Xamarin.Android project is shown below:

The class diagram is shown here, with the classes grouped by layer:

References

The Android app project must reference the platform specific Mono.Android assembly to access classes from the Android SDK.

It must also reference the Core project (eg. Tasky.Core.Android) in order to access the shared data and business layer code.

Application Layer (AL)

Similar to the iOS version we looked at earlier, the Application Layer in the Android version contains platform-specific classes required to ‘bind’ the objects exposed by the Core to the UI.

TaskListAdapter – to display a List<T> of objects (from the Core project) we need to implement an adapter to display custom objects in a ListView. The adapter controls which layout is used for each item in the list – in this case the code uses an Android built-in layout SimpleListItemChecked.

User Interface (UI)

The Android app’s User Interface Layer is a combination of code and XML markup.

  1. Resources/Layout – screen layouts and the row cell design implemented as AXML files. The AXML can be written by hand, or laid-out visually using the Xamarin UI Designer for Android.
  2. Resources/Drawable – images (icons) and custom button.
  3. Screens – Activity subclasses that define each screen and its behavior. Ties together the UI with Application Layer classes and the Core API (TaskManager).

Home Screen

The Home Screen consists of an Activity subclass HomeScreen and the HomeScreen.axml file which defines the layout (position of the button and task list). The screen looks like this:

The Home Screen code defines the handlers for clicking the button and clicking items in the list, as well as populating the list in the OnResume method (so that it reflects changes made in the Task Details Screen). Data is loaded using the Business Layer’s TaskManager and the TaskListAdapter from the Application Layer.

Task Details Screen

The Task Details Screen also consists of an Activity subclass and an AXML layout file. The layout determines the location of the input controls and the C# class defines the behavior to load and save Task objects.

All references to the shared Core library are through the TaskManager class.

Windows Phone App

This diagram illustrates architecture Windows Phone app, where the shared code contains an additional reference to the C#-SQLite assembly, and the Windows Phone application project uses the Controls.Toolkit assembly as well as the SDK.

The complete Windows Phone project is shown below:

The diagram below shows the classes grouped into layers:

References

The platform-specific project must reference the required platform-specific libraries (such as Microsoft.Phone and System.Windows) in order to create a valid Windows Phone application.

It must also reference the Core project (eg. Tasky.Core.WP7) to utilize the Task class and database.

Application Layer (AL)

Again, as with the iOS and Android versions, the application layer consists of the non-visual elements that help to bind data to the user interface.

ViewModels

ViewModels wrap data from the Core (TaskManager) and presents it in way that can be consumed by Silverlight/XAML data binding. This is an example of platform-specific behavior (as discussed in the [Cross Platform Applications] document).

User Interface (UI)

XAML has a unique data-binding capability that can be declared in markup and reduce the amount of code required to display objects

  1. Pages – XAML files and their codebehind define the user interface and reference the ViewModels and the Core project to display and collect data.
  2. Images – Splash screen, background and icon images are a key part of the user interface.

MainPage

The MainPage class uses the TaskListViewModel to display data using XAML’s data-binding features. The page’s DataContext is set to the view model, which is populated asynchronously. The {Binding} syntax in the XAML determines how the data is displayed.

TaskDetailsPage

Each task is displayed by binding the TaskViewModel to the XAML defined in the TaskDetailsPage.xaml. The task data is retrieved via the TaskManager in the Business Layer.

Results

The resulting applications look like this on each platform:

iOS

The application uses iOS-standard user interface design, such as the ‘add’ button being positioned in the navigation bar and using the built-in plus (+) icon. It also uses the default UINavigationController ‘back’ button behavior and supports ‘swipe-to-delete’ in the table.

Android

The Android app uses built-in controls including the built-in layout for rows that require a ‘tick’ displayed. The hardware/system back behavior is supported; there is no back button in the app (as there is in iOS).

 

Windows Phone

Standard Windows Phone layout, including buttons in the app bar at the bottom of the screen and support for the hardware ‘back’ button to navigate.

Summary

This document has provided a detailed explanation of how the principles of layered application design have been applied to a simple application to facilitate code re-use across three mobile platforms: iOS, Android and Windows Phone.

It has described the process used to design the layers and discussed what code & functionality has been implemented in each layer.

The code can be downloaded from github.