Friday, August 21, 2015

Xamarin.Forms - Choosing the right architecture

One of the biggest issues with writing cross platform apps using Xamarin.Forms is that it is hard to know where to start and how to establish good patterns that are scalable enough to support the full app lifecycle from development to maintenance and new feature requests.

This blog post is based on some of the experiences I've had working with on different cross platform apps with Xamarin.Forms.

One of the biggest problems when writing cross platform code that should work on multiple platforms is that you have to choose an architecture and some patterns and stick with them to avoid ending up with code that is hard to maintain and hard to extend with new features.

The architecture and patterns you choose needs to be easy to understand and extensible enough to support future features.

Patterns
My most important principle is to build n app based on loose coupling through dependency injection and then to use the service locator, messaging bus and mvvm patterns to implement the functionality and user experience.

Combining this with unit testing enables me to develop new features quickly while keeping manual testing to a minimum.
I'm using TinyIoC for both dependency injection and as our service locator container and Xamarin.Forms' MessagingCenter as my message bus.
All dependencies on these components are abstracted through interfaces so that they can be swapped out if necessary.

Services for business logic
As a rule of thumb I always write my business logic in service classes that can be configured and injected in my view models. These classes are also testable using a unit testing framework like NUnit.
Server communication
Its common to have a dependency on a cloud service. Such a service is often exposed as a REST based API that can be accessed over HTTPS. Usually I wrap services like this into simple client classes that can communicate with the server and that can be used by my services (never use clients directly in your view models!).

To accomplish this I use the HttpClient library from Microsoft with the modernhttpclient NuGet package to get native speed and performance. For serialization I use Newtonsoft Json.
Persistence
If you have the need for local storage there are many options. I'm using SQLite.Net/SQLite.Net Async since it gives me full async/await support and a nice little ORM that can be used with c# classes. For simple storage needs you can always use a file based library.
Localization
I'm using regular resx files for localization - these work as expected under Xamarin Studio. (you might need to create the localization project in Visual Studio, I've had some issues creating them in Xamarin.Studio - this might have changed by now).
Binding it all together with Mvvm
The mvvm pattern is my default choice for wiring up the business logic and the user interface in my apps. Using ViewModels that transforms data from my services to properties and commands in my view models makes it really easy to get the user experience right. Xamarin.Forms views supports data binding to the view model's properties and commands.
There are several good Mvvm frameworks out there that can be recommended - I suggest you choose a small and simple one with source code.
Custom UX
I almost always have the need for some custom UX in all the apps I'm working on. In Xamarin.Forms you have some excellent extension points called custom renderers that act as bindings between the abstracted and native user elements.

If I have smaller needs like drawing some shapes and/or lines I usually add the NControl library to my project. It lets me create custom controls using the NGraphics abstraction layer. The NControls library is written and maintained by me, you can read more about it in a blog post here.
Analytics
To get knowledge about what your app's users are doing, you should use some kind of library for app analytics. If you can find a library that also handles exceptions and crash reports, you will thank yourself for being wise at an early stage - having stack traces and exception reports from your user's crashes helps finding solutions to strange errors. 

Remember to create an abstraction layer for tracking and error reporting so that you can plug in your own favorite library.

General tips
  • Try to keep the level of customization at a minimum - it is expensive and time consuming and adds to your maintenance costs
  • Follow your patterns - don't be tempted to write business logic in your view models or views
  • Refactor often

Know your platforms - nothing beats having the knowledge about how stuff works on the underlying platforms when it comes to building new functionality and maintaining the old features.