Xamarin is a well-known framework from Microsoft allowing you to use C# to write cross-platform apps for iOS, Android, Tizen and more. With Xamarin Forms, a cross-platform native UI-layer built on top of Xamarin itself, even the views and logic can be shared.
This is great for the object-oriented guys and gals, but what about functionally oriented people? Good news! Xamarin supports F#, Microsoft's functional language, as a first-class citizen on the platform. And just like C#, F# can utilize the entire .Net ecosystem and gives us some exciting choices in regards to app architecture. MVC and MVVM are great for what they are, but haphazardly mutation of state easily leads to bugs and more complicated testing harnesses.
Luckily, with the power of F#, a better way exists.
Fabulous Architecture
Fabulous is a fabulous framework for crafting apps with the ultra-simple Model-View-Update architecture, know from Elm and Flutter, using F# and Xamarin Forms.
The Model-View-Update architecture is easy to reason about. It structures applications into three parts and it defines how these interact with each other. There is only this one determined way to handle interactions and manage state – and it provides a good foundation for modularity, code reuse and testing by default.
Using Fabulous, this is an app in its purest form:
type Msg =
| ...
/// The model from which the view is generated
type Model =
{ ... }
/// Returns the initial state
let init() = { ... }
/// The funtion to update the view
let update (msg:Msg) (model:Model) = ...
/// The view function giving updated content for the page
let view (model: Model) dispatch = ...
type App () as app =
inherit Application ()
let runner =
Program.mkSimple init update view
|> Program.withConsoleTrace
|> XamarinFormsProgram.run app
Fabulous Model
The Model
is the core data from which the whole state of the app can be resurrected. The init
function returns your initial model. The update function updates the model as messages are received.
Fabulous view function
The view
function computes the GUI using the given model. The GUI consists of an immutable object graph of the Xamarin Forms views and controls to be used.
Fabulous update function
Each model has an update
function for message processing. The messages are either messages from the view
or from external events. From these, a new model can be computed.
Fabulous example
The following is a complete example of an app, the source is available on github.
namespace FabulousApp
open Fabulous
open Fabulous.XamarinForms
open Xamarin.Forms
module App =
type Model =
{ Count : int
Step : int
TimerOn: bool }
type Msg =
| Increment
| Decrement
| Reset
| SetStep of int
| TimerToggled of bool
| TimedTick
let initModel = { Count = 0; Step = 1; TimerOn = false }
let init () = initModel, Cmd.none
let timerCmd =
async { do! Async.Sleep 200
return TimedTick }
|> Cmd.ofAsyncMsg
let update msg model =
match msg with
| Increment -> { model with Count = model.Count + model.Step }, Cmd.none
| Decrement -> { model with Count = model.Count - model.Step }, Cmd.none
| Reset -> init ()
| SetStep n -> { model with Step = n }, Cmd.none
| TimerToggled on -> { model with TimerOn = on }, (if on then timerCmd else Cmd.none)
| TimedTick ->
if model.TimerOn then
{ model with Count = model.Count + model.Step }, timerCmd
else
model, Cmd.none
let view (model: Model) dispatch =
View.ContentPage(
backgroundColor = Color.White,
content = View.StackLayout(padding = Thickness 20.0, verticalOptions = LayoutOptions.Center,
children = [
View.Label(text = sprintf "%d" model.Count, fontSize = FontSize.Named(NamedSize.Title), horizontalOptions = LayoutOptions.Center, width=200.0, horizontalTextAlignment=TextAlignment.Center)
View.Button(text = "Increment", command = (fun () -> dispatch Increment), horizontalOptions = LayoutOptions.Center)
View.Button(text = "Decrement", command = (fun () -> dispatch Decrement), horizontalOptions = LayoutOptions.Center)
View.Label(text = "Timer", horizontalOptions = LayoutOptions.Center)
View.Switch(isToggled = model.TimerOn, toggled = (fun on -> dispatch (TimerToggled on.Value)), horizontalOptions = LayoutOptions.Center)
View.Slider(minimumMaximum = (0.0, 10.0), value = double model.Step, valueChanged = (fun args -> dispatch (SetStep (int (args.NewValue + 0.5)))), horizontalOptions = LayoutOptions.FillAndExpand)
View.Label(text = sprintf "Step size: %d" model.Step, horizontalOptions = LayoutOptions.Center)
View.Button(text = "Reset", horizontalOptions = LayoutOptions.Center, command = (fun () -> dispatch Reset), commandCanExecute = (model <> initModel))
]))
let program = Program.mkProgram init update view
type App () as app =
inherit Application ()
let runner =
App.program
|> XamarinFormsProgram.run app
Fabulous advantages
Using immutable models, this architecture has a couple of advantages:
- The
init
,update
andview
functions can be easily tested in isolation - Models can be easily saved and restored
- The
update
function is the only place where your model gets transformed, this structure makes it very easy to reason about state changes and where they are coming from.
And off course, Fabulous supports Live Update, enabling you to make and see the effect of these changes in the simulator without redeploying the app.
Xamarin Forms can truly be fabulous.