Thomas Bandt

MVU For Native Xamarin Apps

While Fabulous provides a great way to build Xamarin.Forms apps with F# and The Elm Architecture, there has been no such opportunity for Xamarin.iOS and Xamarin.Android. Yet. Here it comes.

Published on Thursday, 05 March 2020

Many developers I know buy into the Xamarin.Forms (XF) story, especially those with a background in related technologies out of the Microsoft universe. That is fine, as XF is a decent technology for solving a particular category of problems. Building line of business mobile applications with a simple UI falls into that category.

That being said, I can only recommend looking into Fabulous, as described in an earlier post. It offers a great way to build simple apps with XF written in F# based on an implementation of MVU, the Model-View-Update architecture.

But to be completely honest, other than marketing might tell us, XF is not a silver bullet. At least not to me. At my company we have been working with Xamarin for almost 8 years now. Besides mixed experiences with XF, we think the big deal in regards to the Xamarin stack still is their two original products, Xamarin.iOS and Xamarin.Android.

We use them whenever one of the apps we build requires a sophisticated user interface along with native user experience. Yep, sharing UI code isn't part of that story. But having the freedom to express the UI in a truly native way, e.g., by working with iOS Storyboards, is often worth having two different but mostly thin view implementations.

We usually build those apps with the typical architecture pattern in the Xamarin world, MVVM, and we write them in C#. Since we started to work more and more with F# in the last years, we also checked out how Fabulous might work for us.

Which left us mainly with two questions:

  1. Can't we somehow bring MVU over to our native projects?
  2. Why don't we write the apps in F# in the first place?

At the very least, F# offers many advantages over C#, which matters a great deal, especially for mobile apps. So we spent some time figuring out what our options might look like. Here is a summary of our current state of exploration, which eventually culminated in the proof of concept of the Fabulous for Xamarin Native library.

Xamarin Tooling Is Not Ready For F#

While the quality of Xamarin.iOS and Xamarin.Android and the core tooling around it has improved and stabilized tremendously in the last years, it is more or less heavily focused on C#. Especially for iOS, where working with Xcode and its Interface Builder is an integral part of our development story, this matters a big deal.

Xamarin smartly integrated Xcode: You can create, for example, Storyboards in your app's project in Visual Studio or Rider, and open and edit them in Xcode. Outlets (controls) and things like actions (e.g., click handlers) are then made available to you through automatically generated code. You might, for example, end up with a corresponding ViewController.designer.cs file next to your ViewController.cs, which contains all that generated code.

Unfortunately, that code generation does not work well yet if you use F# instead of C# for your main iOS Xamarin project.

MVVM With F# Does Not Seem Right

Another thing we quickly realized is that MVVM does not make much sense to use in the context of writing apps in F#. Because it would require us to write view models as typical classes, containing members which would then implement INotifyPropertyChanged to be able to be bound to UI elements. This is a lot of object-oriented boilerplate code to write (or to generate). While one of the beautiful things of F# is its ability to cope with object-oriented code in general, this is not a direction we were interested in exploring further because we would switch the language but not benefit from its strengths at this point.

C# + F# Make A Good Couple

So we made a decision: We would keep writing the "frontend" (the UI part, e.g., UIViewController, Activity) in C#. This would allow us to benefit from the existing and production-ready tooling of both Microsoft and Jetbrains. And it would let us write all that object-oriented stuff, that iOS, as well as Android, build their development story upon, in a language that fits perfectly into that world.

The "backend," however, is an entirely different story as it does not depend on any particular fragile toolchain, living in a .NET Standard library anyway.

The core requirement is something that allows us to share functionality across the supported platforms. For example, when a button is being clicked, we want a specific action to be executed. And we don't want to implement that action multiple times. But it does not matter in which language it is written as it is agnostic to the particular "frontend" and its tooling. So for this job, we would pick F#.

Bringing In Fabulous

One of the significant benefits of Fabulous is its ability to express the UI as a pure function. Which also is a great way to have everything in one place inside of a single "program." For more information on that, see the "Full Elmish" section in this post.

While that approach has many advantages, it also requires an additional layer of abstraction on top of the underlying technology, e.g., XAML for XF. To make it easy to use, much complex wiring is required under the hood. Which is fine if said underlying tech does not change frequently. But if it is kind of a moving target, like XF still is, maintaining it can become very tedious and exhaustive rather quickly.

So what we wanted to avoid was to invent a new layer of said abstraction. Instead, we wanted to keep using the original tooling support we were used to and spoiled by (remember: Storyboards 🥳).

Fortunately, the original approach of Fabulous from its early days offered us a starting point: The Half-Elmish implementation, now re-named to Fabulous.StaticView is a good enough compromise. It brings MVU to XF but allows developers to keep building the UI with XAML.

So we would have MVU programs running behind our view controllers and activities instead of traditional MVVM view models. Still, we would not make the UI part of those programs. Instead, we would provide bindings to UI elements as described here.

Introducing Fabulous.XamarinNative

Does that work? It does! At least as a proof of concept, which comes with two small sample apps.

  • The first sample is a typical counter app which shows how bindings between an F# MVU program and a view (a controller on iOS and an activity on Android) can work.

  • The second example demonstrates how multiple controllers can act together. It shows a list of items and provides a separate form to add new items to that list.

As you can see, the programs sharing the logic across platforms (1, 2, 3) are pleasantly simple! For example, take the program responsible for handling the list of people in our second example:

type Model =
    { People: Person [] }

type Msg =
    | PeopleLoaded of Person []
    | CmdLoadPeople

type Program(host: IProgramHost) =
    let loadPeople() =
        PeopleLoaded(PeopleRepository.people)

    let init() = { People = [||] }, Cmd.ofMsg CmdLoadPeople

    let update msg model =
        match msg with
        | PeopleLoaded people -> { model with People = people }, Cmd.none
        | CmdLoadPeople -> model, Cmd.ofMsg (loadPeople())

    let view() =
        [ "People" |> Binding.oneWay (fun m -> m.People) ]

    let runner =
        Program.mkProgram init update view host
        |> Program.withConsoleTrace
        |> Program.run

    do Messenger.subscribe (fun _ -> runner.Dispatch CmdLoadPeople)

Initially, it kicks off loading all the people from some data store. Whenever a new message from another part of the application comes in, it is going to trigger a refresh.

It uses a one-way binding to propagate a property called People in the platform-specific view hosting it. For iOS this is a class called PeopleListViewController:

public partial class PeopleListViewController
    : FabulousUITableViewController<PeopleListProgram.Program>
{
    private Person[] _people;

    public Person[] People
    {
        get => _people;
        set
        {
            _people = value;
            TableView.DataSource = new PeopleTableViewDataSource(_people);
            TableView.ReloadData();
        }
    }

    public PeopleListViewController(IntPtr handle) : base(handle)
    {
    }
}

All that is necessary to start a program for a view is to derive from one of the existing base classes. The program is then automatically created during the normal life-cycle of the view, and all bindings are automatically set in the background.

In this case we do not bind a value to a platform-specific control (like a button), but to a property of the "host". So whenever the people in our model change, the setter for the People property of our PeopleListViewController will be called. Which then kicks of a refresh of the data source of the table view:

Conclusion

While the project at this point is not more than a proof of concept, it might be a promising start. If you are interested in writing native mobile apps in F# and comfortable with having a thin layer of C# on top of it, you might want to look into it.

What do you think? Drop me a line and let me know!