MonoTouch still relies on many of the build and deployment tools that you use for native iOS development and for this reason you need a Mac in order to develop MonoTouch applications. The complete list of tools and hardware required are as follows:
The subject of this article is a cross platform Windows Phone and iPhone application, and unfortunately the Mac environment described above cannot be used for Windows Phone Development. It is possible to dual-boot a Mac with Windows, or use VMWare to run a Windows image, however neither provide the graphics hardware requirements of the Windows Phone emulator. Furthermore, when developing cross platform applications it is highly beneficial to be able to make changes to the shared codebase and build / test for all platforms rapidly. For this reason, a dual boot Mac would not be a terribly practical solution.
With this setup I can move seamlessly between the Windows Phone and iPhone codebase, and changes to the common core are immediately synchronized between each.
The de-factor pattern for Windows Phone development is the Model-View-ViewModel (MVVM) pattern, where view logic resides within the ViewModel, and synchronisation with the ‘dumb’ View is achieved via the built-in binding framework.
The iOS APIs lack a binding framework, so the MVVM pattern is not the most appropriate to use for iOS, or cross-platform, development. There are open source MVVM implementations for iOS, but within this article I don’t want to add more frameworks to the mix, so instead, I will make use of a different UI design pattern, one that does not rely on databinding.
The MVVM pattern is, in my opinion, one of the simplest UI design patterns. It is comprised of three components, the model, which contains application data and services, the view model, which contains application logic, and the view, which renders the view model.
One of the key principles of the MVVM pattern is that the view should be as ‘dumb’ as possible, in order to maximise test coverage. For more detail on this approach, see Martin Fowlers writings on the ‘passive view’.
As a result of this principle, the view model contains view-specific state, such as whether a button is enabled, or perhaps even the color of a button. For this reason, the view model is often thought of as a ‘model of the view’.
With the MVVM pattern the view model holds a reference to the model, but not vice-versa. Likewise, the view has a reference to the view model (typically via the DataContext property), but not vice-versa. This results in a simple, linear diagram:
Changes in view model state are reflected in the view via bindings.
In the absence of a binding framework, there is no automatic mechanism for transferring changes in view model state onto the view. In this case, the view model must ‘push’ changes to the view by directly invoking methods on the view. This results in a pattern known as the Model-View-Presenter (MVP) pattern:
Adopting the MVP pattern is quite straightforward, you can still structure your models in much the same way that you would using the MVVM pattern. The main difference is that there is just a little bit more code to write!
Application Structure
In the next sections I will cover the implementation of both versions of the application in parallel; this mirrors the actual development process where it makes sense to develop both versions side-by-side.
The code shared between the iOS and Windows Phone applications is the entirety of the model and presenter layers. The Property Finder makes use of the Nestoria JSON APIs for querying their UK property database. The primary responsibility of the model layer is to hide the details of this web service, to present a C# API for querying properties.
I’m not going to describe the model layer in detail, it’s pretty un-interesting! Instead I’ll just show a few classes so that you can understand the overall ‘shape’ of this layer.
The Nestoria APIs allows you to search by a text-based search string or a geo-location. The model layer has an interface which described this service:
public interface IJsonPropertySearch
{
void FindProperties(string location, int pageNumber, Action<string> callback);
void FindProperties(double latitude, double longitude, int pageNumber, Action<string> callback);
}
The implementation of this interface is quite straightforward, each of the above methods uses a different query URL, so we’ll just look at the implementation of one of them:
public void FindProperties(string location, int pageNumber, Action<string> callback, Action<Exception> error)
{
var parameters = new Dictionary<string,object>(_commonParams);
parameters.Add("place_name", location);
parameters.Add("page", pageNumber);
string url = "http://api.nestoria.co.uk/api?" + ToQueryString(parameters);
ExecuteWebRequest(url, callback, error);
}
ToQueryString
is a utility method that creates a URL, adding query string parameters using the supplied dictionary:
private string ToQueryString(Dictionary<string, object> parameters)
{
var items = parameters.Keys.Select(
key => String.Format("{0}={1}", key, parameters[key].ToString())).ToArray();
return String.Join("&", items);
}
The ExecuteWebRequest
method uses the WebClient
class to perform an asynchronous request. But this is where things get a little complicated:
private void ExecuteWebRequest (string url, Action<string> callback, Action<Exception> error)
{
WebClient webClient = new WebClient();
Timer timer = null;
TimerCallback timerCallback = state =>
{
timer.Dispose();
webClient.CancelAsync();
error(new TimeoutException());
};
timer = new Timer(timerCallback, null, 5000, 5000);
webClient.DownloadStringCompleted += (s, e) =>
{
timer.Dispose();
try
{
string result = e.Result;
_marshal.Invoke(() => callback(result));
}
catch (Exception ex)
{
_marshal.Invoke(() => error(ex));
}
};
webClient.DownloadStringAsync(new Uri(url));
}
There are a couple of areas where the above code differs from the code you would most likely write if this was not being shared with a MonoTouch application …
The above code uses a timer in order to ‘timeout’ a web request that runs for too long. Within Windows Phone applications the easiest timer to use is a DispatcherTimer
, however, this class is part of the Silverlight framework which is not supported by MonoTouch. Instead the above code uses a System.Threading.Timer, which is available in MonoTouch. For a comprehensive review of the differences between the various .NET timers, see this MSDN article.
The second difference is in the usage of WebClient
. When used on a Windows platform (Silverlight, WPF, WinForms), the web request is performed on a background thread in order to keep the UI responsive, but when the download is completed the DownloadStringCompleted
event is fired on the UI thread – for ease of use. I found through trial and error that in MonoTouch, DownloadStringCompleted
is not fired on the UI thread. iOS has a similar threading model to Windows Phone were UI controls have thread affinity, meaning that you should only change their state from the UI thread.
To solve this issue, the above code uses an instance of the following interface:
public interface IMarshalInvokeService
{
void Invoke(Action action);
}
The Windows Phone implementation does nothing, other than invoke the action immediately …
public class MarshalInvokeService : IMarshalInvokeService
{
public void Invoke(Action action)
{
action();
}
}
Whereas the iOS version uses the NSObject.InvokeOnMainThread
method to marshal the event back onto the UI thread:
public class MarshalInvokeService : IMarshalInvokeService
{
private NSObject _obj = new NSObject();
public MarshalInvokeService ()
{
}
public void Invoke (Action action)
{
_obj.InvokeOnMainThread(() => action());
}
}
I will cover this approach to resolving framework differences that impact the model and presenter layer using ‘services’ in a bit more detail later. For now it is interesting to note how the requirement to share this code between Windows Phone and iOS has had a small impact on the code structure.
Because this is a C# application (as opposed to JavaScript), string-based JSON data returned by IJsonPropertySearch
is not the most natural format to be passing between application layers. The PropertyDataSource
class is the primary interface for the model layer, and is responsible for converting the JSON data into an equivalent C# representation:
public class PropertyDataSource
{
private IJsonPropertySearch _jsonPropertySearch;
public PropertyDataSource(IJsonPropertySearch jsonPropertySearch)
{
_jsonPropertySearch = jsonPropertySearch;
}
public void FindProperties(double latitude, double longitude, int pageNumber, Action<PropertyDataSourceResult> callback)
{
_jsonPropertySearch.FindProperties(latitude, longitude, pageNumber,
response => HandleResponse(response, callback));
}
public void FindProperties(string searchText, int pageNumber, Action<PropertyDataSourceResult> callback)
{
_jsonPropertySearch.FindProperties(searchText, pageNumber,
response => HandleResponse(response, callback));
}
private void HandleResponse(string jsonResponse, Action<PropertyDataSourceResult> callback)
{
JObject json = JObject.Parse(jsonResponse);
string responseCode = (string)json["response"]["application_response_code"];
if (responseCode == "100" ||
responseCode == "101" ||
responseCode == "110" )
{
var result = new PropertyListingsResult(json);
callback(result);
}
else if (responseCode == "200" ||
responseCode == "202" )
{
var result = new PropertyLocationsResult(json);
callback(result);
}
else
{
callback(new PropertyUnknownLocationResult());
};
}
}