Working with Fabulous for Xamarin.Forms

Working with Fabulous for Xamarin.Forms

 

Greetings reader from the future! Take heed that what your are about to read was still relevant as of March 2019 and Fabulous 0.33.1, before the robot wars changed everything.

So what is this thing?

Fabulous is a functional framework built on top of Xamarin Forms to allow for a stronger and more complete functional experience, started by none other than F#'s Don Syme.

However, this post is not intended as an introduction to Fabulous; if you are reading this I am taking it for granted that you have at least a passing familiarity the Fabulous framework, and instead focus on rambling a bit about how it went for me working with it.

If you want to learn more about Fabulous or the MVU design pattern and elm/elmish you should take minute to visit these links:

Spoilers: I think it's pretty great. If I describe some current omission that you think is a deal breaker, remember that it's quite a young framework with a lot of very competent people working on it, so be sure to check back.

What's it like, in practice?

While F# has been an option for XF development for some time now, Fabulous is a big step away from a style of development that in practice was more about translating C# to F# in one's head and far less about leveraging functional tropes unless deep into your backend logic. I remember writing a lot of awkward classes, as well as headscratching about how a custom Bindable Property should ideally look like in F#, while simultaneously trying to squeeze domain modelling logic through the view model complement of a XAML page.

As a C# XF developer I was a big proponent of XAML as the more expressive way to organize your UI in Xamarin, not least because it was a natural jumping point for keeping front end and back end concerns mostly separate.

The alternative of course being building the GUI via code-behind component declarations in C#, which when attempted seemed to always result in overlong and convoluted functions, and correspondingly unwieldy view-to-model binding code. There was also the added complexity of separately managing DependencyProperties such as using Grid.Row and Grid.Column to position a child in a Grid Layout. Needless to say, maintainability was often an issue.

Owing to the early days of Xamarin Forms and how debugging XAML and XAML binding was problematic to say the least, there's probably a lot more code of that sort lurking about than there should ever have been.

Even though it became quickly obvious that the conciseness of F# syntax allowed for shorter and more compartmentalizable model-viewmodel code, it wasn't until using Fabulous that I finally felt comfortable with abandoning XAML/MVVM altogether.

This was a big thing for me.

Coming at this from an almost exclusively C#/XAML MVVM standpoint it was actually kind of a rude awakening, the realization of how much of what amounts to boilerplate code I always ended up writing, be it IValueConverters and custom Bindable Properties or the occasional over-verbosity of XAML itself (XAML vs Fabulous grouped lists is a good example that we will later see).

In fact, while initially trying out Fabulous I soon became legitimately anxious; what if this thing turned out to be ridiculously buggy or otherwise didn't pan out? It was going to be such a shame, because then moving back to C#/XAML would seem like an incredible chore.

Fabulous was basically ruining vanilla Xamarin Forms for me!

Cue flashbacks to a whole host of things that we never completely settled on a definitive way to do, such as how returning data from a modal page always seemed to involve events and ultimately seemingly unnecessarily interdependent models (MessagingCenter notwithstanding), or how to make sure bound commands block other commands (and themselves!) from running at the same time, that now seemed non-issues.

I mean, even if these few things that sprung to mind turn out to be more of an indication of my shortcomings as a Xamarin Forms developer than an indictment of the XAML/C# model itself, isn't it like the FSharpest thing ever that I am basically unable to create these types of issues in Fabulous?

Let's dial down the drama and break it down a bit

There's so much less verbosity and boilerplate code

No bindable properties, no IValueConverters, no Style Setters, no DataTemplates and DataTemplate selectors, no ResourceDictionarys, all are either made redundant or abstracted away. In fact, no binding maintenance code whatsoever, and no XAML.

No XAML

MVU seems a really good fit for Xamarin Forms and the ViewElement API for controls is expressive enough that I feel comfortable not spreading the GUI code to an XAML file. If on the other hand you want some of the benefits of Fabulous while not abandoning XAML completely, you can try the so called half-elmish approach.

MVU design pattern

Now that you aren't writing as much code, you can spend the extra time thinking about the logic of your application, especially since the MVU design pattern forces you to bring domain modelling concerns front and center as soon as you start building a new page. To me it all feels more natural and a lot more focused.

Good documentation to get you started

The documentation offers broad coverage of topics, is concise and well written and generally well above average in every way compared to open source project documentations.

However, I did find myself relying a lot on the existing sample projects and the Fabulous codebase itself to find implementation details. As I'll get into later, this is not a documentation problem as much as it is a lack of a specific task centered examples (i.e. how do I ask for device permission and then respond to the result?) and commonly accepted architectural guidelines.

State serialization out of the box

Is already taken care of with no extra work, as long as you can keep track of any model properties that aren't json serializable out of the box.

Or you can do your own thing, the centralized model logic and the one-to-one correspondence between model state and view state certainly makes any serialization endeavors easier and more obvious.

Control components affinity:

The Fabulous ViewElement controls (i.e. Label is now wrapped in View.Label) are not just MVU wrappers for the existing controls, but also contain design choices that help smooth them over into F# as well as do away with much of the verbosity of the original implementations.

For instance, here's how to declare a Grid layout control with some rows and columns in all idioms:

XAML

<Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="2*" />
    <RowDefinition Height="*" />
    <RowDefinition Height="200" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto" />
  </Grid.ColumnDefinitions>
</Grid>

C#

var grid = new Grid
{
    RowDefinitions = new RowDefinitionCollection
    {
        new RowDefinition { Height = new GridLength(2, GridUnitType.Star) },
        new RowDefinition { Height = new GridLength(1, GridUnitType.Star) },
        new RowDefinition { Height = new GridLength(200) }
    },
    ColumnDefinitions = new ColumnDefinitionCollection
    {
        new ColumnDefinition { Width = GridLength.Auto }
    }
};

This is actually an improvement to what I was used to, as I'm fairly certain definition collections were read-only for the longest time. The official documentation seems to bear this out, as at the time of writing they are still using grid.RowDefinitions.Add in their examples.

Fabulous

View.Grid(
    rowdefs = ["2*";"*";200.],
    coldefs = ["auto"] )

Neat, huh?

Here's how you do a grouped list with multiple different data templates for the group headers and the list items:

View.ListViewGrouped(
     hasUnevenRows = true, showJumpList=true,
     items = [     
        "Index of B", View.Label "Header for B", [ View.Label "Child-1"; View.Label "Child-2"] 
        "Index of C", View.Frame(content=View.Label(text="Other header")), [ View.Label "C-1"; View.Frame(backgroundColor=Color.Red) ]
        "Index of G", View.Label "Header for G", [ View.Label "Gibbon"; View.Label "Golden Lion Tamarin" ]
        ...
     ])

That's right, as long as your item list is typed string\*ViewElement\*ViewElement list you can add ad hoc item renderers at will, without having to bother with DataTemplates and DataTemplateSelectors. Making a special List inherited class to store each group is also unnecessary, as is declaring specific fields for binding.

Meanwhile, in C#/XAML

<ListView ItemsSource="{Binding ListOfPeople}"
          IsGroupingEnabled="true">
    <ListView.GroupHeaderTemplate>
        <DataTemplate>
            <ViewCell>
                <Label Text="{Binding Heading}" />
            </ViewCell>
        </DataTemplate>
    </ListView.GroupHeaderTemplate>

    <ListView.ItemTemplate>
        <DataTemplate>
            <ViewCell>
               <Label Text="{Binding DisplayName}" />
            </ViewCell>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string DisplayName
    {
        get
        {
            return $"{LastName}, {FirstName}";
        }
    }
}

public class PersonList : List<Person>
{
    public string Heading { get; set; }
    public List<Person> Persons => this;
}

And this is before adding any multi-template functionality.

What about 3rd Party Components?

Fabulous plays well with them, with a couple of important caveats:

  1. At this stage if you want to integrate 3rd party visual components to the MVU you'll likely have to provide the ViewElement wrapper yourself by writing some tedious boilerplate, and perhaps sneak in a cool new feature or two as in the Component Affinity section. Here's the wrapping instructions with a list of ready made components so far.
  2. You can instantiate them the old fashioned way in code, but you'll have to treat them as design pattern foreigners. This is good enough for many cases, especially if you're dealing with a visual component that was always meant to be used outside of your normal view stack, like a barcode scanner that raises a modal window with camera port and returns a result string.
  3. The documentation makes it pretty clear that the extension API is subject to change.
  4. It follows that the current API is kind of underdocumented; in my case there was a lot of trial and error and ctrl+fing the source code until I got the hang of it.

Luckily you can get away with including only what you specifically need in the view extension, there's no need to do a complete port of a third party component to make it integratable in an MVU view.

The documentation mentions that a code generator or a type provider might be forthcoming, which is a development that I certainly would find welcome.

So what's the catch?

  • It seems it's still a small community. Go us!

  • With Xamarin.Forms 3.6 just released, API support for Fabulous as of now is up to 3.4, with the 3.5 PR pending review. You can still reference the newer XF packages and benefit from bug fixes and performance enhancements, but I assume new API options might not be available in the ViewElement controls.

  • If you've never worked with elmish before then the MVU design pattern might take some getting used to. If this is also your first foray into F# and functional development you might be trying to juggle a few too many bowling pins for a beginner project, so take care.

  • For developers new to Xamarin: as mentioned before parameter versatility via the obj type comes with the cost that intellisense can't help you choose the right type. For now this seems like a step down from the current state of XAML autocomplete.

    Also, when some of the not so intuitive parameters break you might need to look them up in the Fabulous codebase to find out what the proper input format is meant to be. A makeThickness has failed runtime error for accidentally typing margin=20 instead of margin=20. with a dot is likely to catch quite a few people unawares.

  • While the documentation is more than good enough to get you started, and a healthy library of sample projects is beginning to take shape, what's so far missing is a guide for common tasks, basically a cookbook. This is to say that it would be far simpler if googling Fabulous + check device permission got you straight to some relevant snippet, rather than needing to comb the documentation and have a House MD type epiphany on seeing Updates and Messages > Optional Commands > Cmd.ofAsyncMsgOption.

    I'm sure this is going to improve rapidly, and for my part I will soon be uploading a few such examples of cases that had me go 'hmmmmm' for a while before going 'a-ha!'.

  • In the same vein it'd be great if a set of general guidelines started to emerge. At times I felt that I was making things up as I went along that I shouldn't be, as in, what is the best way to do X?

What is the best way to do X?

Since I think the only post in the Xamarin Community Forums about Fabulous is still mine, and the scope of these questions is probably too broad for stackoverflow, I might as well get the ball rolling here, instead of for instance flood github comments or annoy everyone on twitter with what might well turn out to be redundant or irrelevant questions. Feel free to join discussion, I have comments now!

Look for this section to be edited if there is interesting feedback.

Handling frequently changing values from components

What is the best way to use MVU in order to get data from Entry and Editor components? Surely we shouldn't be updating the view at every keystroke!

I've head success with using a ViewRef to get the necessary values, and using an intermediate mutable variable that is not a part of the model to store the changing text also seems to work.

I will update the post with an example of a view with a filterable list to better illustrate the point.

Preventing big updates from tiny changes

How should we handle changing properties like ContentPage's IsBusy or Title? Do I understand correctly that doing it through the model might cause the entire page stack to be redrawn?

Wrapping everything else in a ContentPage except IsBusy in a dependsOn function every time seems like a lot of work. Perhaps a complement to dependsOn where we would provide only properties that we don't want to cause an update would help.

How to store the currently selected item from a ListView

This is a case that seems to combine the two previous concerns. This is also something that we are more likely to want to be in the model in order to get serialized if the app goes to the background.

Of note also that MVU ListView currently works with returning the index of a selected item (or a tuple of group and line index in case of grouped list).

Updating submodels

On the subject of dependsOn: If we have an app that goes some pages deep, is it a no-brainer to always use dependsOn on the corresponding sub models? Or is that something that already taken care of behind the scenes?

That said it's probably an anti-pattern to keep around views that are normally further down the navigation stack, but I suppose I wouldn't like it if a message from a view down the stack that changed something in the main model caused all views on the stack to be regenerated every time.

Keeping messages in pushed pages from leaking to the root view

Are there any drawbacks to having dispatched messages always passed through the main view? On the flip-side, are there tasks that are better off handled directly in the root update loop, like maybe alerts?

I like the pattern shown in the ElmishContacts sample project, where global messages are handled separately, but I haven't yet looked into it as thoroughly as I'd like to.

It also seems like a nice subject for a tutorial, since it combines messages, Cmds, navigation and updates.

On the subject of pushed pages,

What's the best way to expand our application layout in depth?

Declaring the sub page models with Option and then showing the subviews according to the models that are not set to None seems like a good start.

Best ways to organize code

As pages get more complex, F#'s order of declaration constraint means that an increasing amount of code (especially GUI fragments that need dispatch) can only be declared between the message type declaration and the update function, which isn't necessarily optimal in the long run.

Expanding complex user controls to their own MVU file may be one way to compartmentalize them out of their host page.

Cmd.ofOption or Msg.DoNothing

I noticed that some examples use a DoNothing or NoOp message. Isn't this a bit redundant since we already have Cmd.ofOption that only propels forward Some Msg? Or are there cases where we absolutely must dispatch a message even if it's to tell the application to do nothing and maintain the state?

Map Pins as ViewElements

Originally I was going to ask why Pins are implemented as ViewElements in the map extension since they appear to be just structs, but then came Xamarin.Forms 3.6 with templatable map pins, and I went a-ha!

Partial serialization

Do we just use the [<JsonIgnore>] attribute over unserializable properties or properties that we don't wan't serialized, or want to serialize in a custom manner? Is there an optimal pattern for integrating the regeneration of such properties when the app wakes up?

Speed

Feels normal at what I attempted, and I haven't seen any benchmarks that say otherwise. The infinite list implementation seems very promising.

But I'd still much like to see side by side benchmark comparisons to vanilla Xamarin.Forms .

Conclusion

As far as I'm concerned, I'm not going back to vanilla Xamarin.Forms if I can help it, and I'm crossing every pair of fingers I have that there's no humongous deal breaker down the line that I haven't yet stumbled onto, like if release builds only work with Comic Sans in progammer's pink or that control responsiveness falls to zero if there's a prime number of labels in the same container.

For now, I feel the main thing Fabulous is missing is for someone to write the book on it, so new developers can spend less time poking around to discover how certain things work. Also, the sooner 3rd party control support is streamlined via a type provider or a code generator, or both, the better.

Thanks for reading, have a great day!

Coming up next...

I want to clean up some Fabulous examples and make them available, either as an addendum to this post or on their own.

Then probably the MNIST IfSharp notebook, HMB while I try to include a minimalist drawing canvas widget for live testing the digit recognition model inside the notebook.

Cheers! -- Ares

 

Links in article