Wednesday, March 29, 2017

Xamarin Forms Observations

Early 2022 note: The Xamarin platform has improved a lot since this article was written. Overall stability has greatly improved, there is a richer set of controls and libraries, and the UI design experience can be regarded as acceptable. More importantly though, Xamarin has evolved into the MAUI platform, so this article will soon become a historical curiosity. I expect to make new posts about MAUI after I start using it later this year (2022).


The Xamarin development platform attempts to abstract and unify the development and distribution of apps for iOS, Android and Windows mobile devices. This is incredibly ambitious considering the vastly different technical details and cultural histories of the platforms. It's admirable of Xamarin to attempt to do this, but I must sadly report that they are only barely succeeding.

I have been using Xamarin Forms with Xamarin Studio on an iMac for almost two years, experimentally at first, and with intensity for the last several months to produce live apps. In that time I must also sadly report that Xamarin is one of the worst development platforms I have used in 30 years. It is unpredictable, unstable and riddled with quirks and 'gotchas'. Operating system updates or public library updates from Xamarin can break your development cycle utterly and hours (or days) of research may be required to get back-on-track. Debuggers will stall or not start, bizarre compile errors may appear, breakpoints stop in inaccessible places, intellisense produces nonsense, runtime app appearance is inconsistent on different platforms, and much more ... from the other end of the house my wife will often hear me shout the Xamarin mantra "everything f***ing doesn't work", which I will probably have engraved on my tombstone.

As a result of my suffering I have decided to document some observations and hints for other developers who may follow in my path of Xamarin development.

What I'm going to say assumes that you are writing "modest" sized apps with perhaps a handful of screens. By their nature, mobile apps should be easy to use and avoid deep navigation, otherwise users get confused and irritable. Apps should also not contain complex business logic, which should be pushed into the backend. So the following observations I'm making are relevant to apps of modest size and complexity. In cases where you have a complex app and a large team of experienced developers, then you can take my advice with a grain of salt. All of the apps I have written or seen in recent years have been of "modest" size.

Minimal Packages and Libraries


Don't use a Nuget package or library unless you really need it. There are libraries for busy controls, animated effects, platform specific bridges, dialogs, reactive events, IoC and MVVM patterns, service calls, and much more. Avoid them. The more "stuff" you put into an app, the more bad "stuff" happens. Many libraries do things can be done in standard code, but they obfuscate what's happening, complicate debugging and create "magical" code.

For example, I had an app that originally used a dozen various libraries and it would unpredictably crash on startup on certain Android devices and it would produce random crash reports with a stack trace deep inside a 3rd party library. After stripping out Acr.DeviceInfo, Acr.Userdialogs, Fody Weavers, FreshMVVM, FreshEssentials, Microsoft.Bcl, Refit, Rx (Reactive Extensions), SlideOverKit and Plugin.Connectivity, all of those problems vanished. Otherwise it could have taken weeks of effort to diagnose and solve the problems.

Lots of libraries also mean you might have complicated dependencies between them, which I found would make upgrading them a fragile process.

There are of course times when you are compelled to use a library, such as for charting, ink writing capture or exploiting device specific features. In this case, proceed by single steps, methodically, testing as you go, and keep praying!

Application Structure


Last week I was asking some highly experienced Xamarin developers what conventions or patterns they have found most popular (or best?) for the overall structure of a Xamarin Forms app. The answer was basically "whatever you or your team like". Which I found quite worrying.

I have seen a small Xamarin Forms app with 6 pages broken into 6 models, a service bus, IL weaving of binding properties, inter-controller messaging, Rx callbacks, etc. I haven't seen this sort of complexity since I worked on a huge Windows Forms program with 90 screens and dialogs about ten years ago.

I believe there is no need to over-engineer a small mobile app with complicated infrastructure, libraries and patterns. As a result of my recent experience, I have decided on a convention for how to structure a Xamarin Forms app that is simple and based upon classical MVVM. The app is basically two parts: the portable controller and the portable mobile project. Here's an overview.
Controller
The behaviour of the application is extracted into a single controller class in a portable library. The controller class implements INotifyPropertyChanged in the classical way. The controller class is effectively a state machine consisting of bindable properties and public methods. If there are large numbers of bindable properties, then you can use a T4 template to generate them into a partial class.
Mobile App
The mobile Application class sets an instance of the controller as the BindingContext in the App constructor just before the MainPage is set. The whole mobile app and all of the pages it contains are therefore automatically bound to the controller. The app binds its controls to the notify properties and calls the controller's methods to do work. As the controller does its work, it sets the properties and the app's UI responds. An IValueConverter class may be needed between some controls and properties.
What I'm describing here is the simplest form of classical MVVM possible, where a UI is bound to a controller full of notify properties. I have found it to be simple and effective for writing modest sized Xamarin Forms apps. The controller class is decoupled from the UI and can be subjected to stand-alone unit testing. The controller can also be used to easily drive other types of programs such as Silverlight, WPF, ASP.NET, or even a console command.

Here's simple skeleton code of how I would code a login process. The login page binds Entry controls to the UserName and Password. Clicking a button calls the Login() method. After the method runs the controller changes state as properties are changed and the UI automatically responds.


public class AppController : INotifyPropertyChanged
{
  public string UserName { ...notify property... }
  public string Password { ...notify property... }
  public Login Data { ...notify property... }
  public bool IsLoginBusy { ...notify property... }
  public Exception Error { ...notify property... }
  public async Task Login()
  {
    try
    {
      IsLoginBusy = true;
      Data = await service.RemoteLogin(_username, _password);
      Error = null;
    }
    catch (Exception ex)
    {
      Data = null;
      Error = ex;
    }
    finally
    {
      IsLoginBusy = false;
    }
  }
}


It's important to think of the controller as a state machine. As Login() processes, the controller's properties change and finally indicate if it's now in the login-success or login-error state. The UI is designed to react to changes in state by binding. The visibility of a spinner could be bound to the IsLoginBusy property. If the login fails for example, an error label would become visible if the Error property is not null.

Some people try to abstract away the page navigation, but I have found this to be monstrous overkill. I believe navigation is a UI related task, so in the login sample, upon login-success I would simply put this single line of code in the code-behind.

Application.Current.MainPage = new FooPage();

Other Notes

  • Don't make too many changes at once or you'll break something and go through hell trying to backtrack and find out what's broken.
  • Don't try to target Windows except in the most trivial of app cases. This is especially true for tablet apps which look clumsy and non-standard when generated by Xamarin Forms. Write Windows tablet apps as UWP projects in Visual Studio and use the native controls like the CommandBar which give your app a natural feel on the platform.
  • Accept updates for Xamarin Studio, Mono .NET, Xcode and other libraries with the greatest care. As I said earlier, updates can suddenly break your development.
  • If you find breakpoints are not working, or stopping in weird locations, then close your dev apps, delete obj and bin folders from all projects and start again.
  • You'll probably need the following special controls: (1) An iOS Entry control that doesn't perform spelling checks and capitalise (2) A bindable pickerNote-1 (3) A zoomable web view. There are samples of all of these available online, but be sure to use a simple one as many are over-engineered.
  • Place copies of all shared images in one platform project only, then add links to them from the other project.
  • I have never been able to get an Android emulator to work correctly. For several weeks I had a tablet emulator running VERY SLOWLY, then one day it stopped and never worked again (it just says "deploy failed"). I can only test by plugging a device into the iMac via a USB cable, and even then it's unreliable and I may have to take the cable in-and-out many times before it's recognised.
Note-1 Xamarin Forms now provides a Picker class with familiar bindable properties. It seems to have a bug though, as when the parent page is disposed, the SelectedItem property always seems to be set to null.

Service Calls

Your controller class will typically contact the "outside world" via web service calls, or perhaps to a local SQLite database (or Couchbase Lite which internally uses SQLite). In large applications many developers turn this level of code into an art form by the use of Dependency Injection (DI) to strictly decouple the service calls from the app. There is nothing to stop you doing this in a Xamarin Forms mobile app, in fact there are plenty of free frameworks available to encourage you, and there are lots of videos and tutorials on the subject.

Continuing my claim that most mobile apps are of "modest" size, I think that using a DI framework is usually overkill. Just use common sense and put all service or database work behind an interface, then use some simple pattern like a service locator or even a static helper class to get implementation references.

You could, for example, use the Refit library to turn a REST API into an interface.  And although I think this library is very clever and neat, I have previously received random crash reports from deep inside the library and have been unable to diagnose them. It was easier to remove Refit and replace the service calls with my own code, and the crashes vanished. I only had about 10 distinct service calls anyway, so after refactoring the code it was only marginally larger than the original, and it simplified debugging.

No comments:

Post a Comment