Fabulous - Guide
- Getting started
- Update and Messages
- Traces and Crashes
- Unit testing
- Further Resources
There is an experimental LiveUpdate mechanism available. The aim of this is primarily to enable modifying the
view function in order
to see the effect of adjusting of visual options.
At the time of writing this has been trialled with
- Visual Studio + Android (USB Device or Emulator)
- Visual Studio for Mac + Android (USB Device or Emulator)
- Visual Studio for Mac + iOS (USB Device or Emulator)
Some manual set-up is required. The following assumes your app is called
Install or update
fabulous-clias a global tool
dotnet tool install -g fabulous-cli dotnet tool update -g fabulous-cli
Check your projects have a reference to nuget package
Fabulous.LiveUpdatefor all projects in your app. This is the default for apps created with templates 0.13.10 and higher. Do a clean build.
Uncomment or add the code in the
#ifsection below in
type App () = inherit Application() .... #if DEBUG do runner.EnableLiveUpdate () #endif
If running on Android, forward requests from localhost to the Android Debug Bridge:
USB: adb -d forward tcp:9867 tcp:9867 EMULATOR: adb -e forward tcp:9867 tcp:9867
Launch your app in Debug mode (note: you can use Release mode but must set Linking options to
Run the following from your core project directory (e.g.
cd SqueakyApp\SqueakyApp fabulous --watch --send
Now, whenever you save a file in your core project directory, the
fabulous watcher will attempt to recompile your changed file and
send a representation of its contents to your app via a PUT request to the given webhook. The app then deserializes this representation and
adds the declarations to an F# interpreter. This interpreter will make some reflective calls into the existing libraries on device.
To take effect as app changes, your code must have a single declaration in some module called
program taking no arguments. For example:
module App = ... let init() = ... let update model msg = ... let view model dispatch = ... let program = Program.mkProgram init update view
If a declaration like this is found the
program object replaces the currently running Elmish program and the view is updated.
The model state of the app is re-initialized.
The F# interpreter used on-device has incompletenesses and behavioural differences:
- Object expressions may not be interpreted
- Implementations of ToString() and other overrides will be ignored
- Some other F# constructs are not supported (e.g. address-of operations, new delegate)
- Some overloading of methods by type is not supported (overloading by argument count is ok)
You can move generally move problematic constructs to a utility library, which will then be executed as compiled code.
Changes to the resources in a project (e.g. images) require a rebuild
Changes to Android and iOS projects require a rebuild
You can’t debug interpreted code from the IDE using breakpoints, stack inspection etc. Restart for that.
You may need to mock any platform-specific helpers you pass through, e.g.
module App = ... let init() = ... let update (helper1, helper2) model msg = ... let view model dispatch = ... #if DEBUG // The fake program, used when LiveUpdate is activated and a program change has been made module AppLiveUpdate = open App let mockHelper1 () = ... let mockHelper2 () = ... let programLiveUpdate = Program.mkProgram init (update (mockHelper1, mockHelper2)) view #endif type App (helper1, helper2) = inherit Application() .... // The real program, used when LiveUpdate is not activated or a program change has not been made let program = Program.mkProgram App.init (App.update (helper1, helper2)) App.view
There may be issues running on networks with network policy restrictions
The LiveUpdate mechanism is very experimental.
- Debug output is printed to console by
- Debug output is printed to app-output by the on-device web server
The fabulous watcher does this:
Cracks project options, listens for changes, then uses FSharp.Compiler.Service to compile
converts code output to PortaCode code model
serializes PortaCode using Newtonsoft.Json
sends to device by http.
Device app does this:
starts httplistener, which gets http request
uses Interpreter.fs to run.
looks for a “program” declaration in interpreted code and hacks into the currently running Elmish app and replaces the Fabulous “program” ie view/update/init logic.
Device app continues to use whatever library dlls are on device via reflection.
Please contribute documentation, updates and fixes to make the experience simpler.