Using the Fabulous command line
With the fabulous-cli
you can run LiveUpdate.
Install fabulous-cli
:
// install latest
dotnet tool install -g fabulous-cli
// install explicit version
dotnet tool install -g fabulous-cli --version {versionnumber}
Update fabulous-cli
:
dotnet tool update -g fabulous-cli
Uninstall fabulous-cli
:
dotnet tool uninstall -g fabulous-cli
Live Update
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)
- Visual Studio + WPF
Some manual set-up is required.
- Install or update
fabulous-cli
as a global tool
dotnet tool install -g fabulous-cli
dotnet tool update -g fabulous-cli
Install or update the NuGet package
Fabulous.XamarinForms.LiveUpdate
for all projects in your app.
This is the default for apps created with templates 0.13.10 and higher. Do a clean build.Add the following reference to enable live update:
open Fabulous.XamarinForms.LiveUpdate
- Uncomment or add the code in the
#if
section below:
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
None
rather thanSDK Assemblies
)Run the following from your core project directory (e.g.
SqueakyApp\SqueakyApp
)
cd SqueakyApp\SqueakyApp
fabulous --watch --send
- It may be necessary to launch Visual Studio with elevated permissions (“Run as administrator”); otherwise,
runner.EnableLiveUpdate()
may fail to start the HttpListener, which will cause LiveUpdate to fail.
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 programLiveUpdate
or 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.
Known limitations
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
In Visual Studio 2019, by default you cannot edit files whilst debugging. To enable file edits, turn OFF Edit and Continue by going to Tools->Options, selecting Debug->General and unchecking Enable Edit and Continue.
Troubleshooting
The LiveUpdate mechanism is very experimental.
- Debug output is printed to console by
fabulous
- Debug output is printed to app-output by the on-device web server
ERROR SENDING TO WEBHOOK: "System.Net.WebException: No connection could be made because the target machine actively refused it."
If the LiveUpdate console displays this error there are multiple possilbe causes:
- Visual Studio needs elevated permissions to execute “EnableLiveUpdate()”.
- Solution: launch Visual Studio using the “Run as Administrator” option
- Connection to wrong webhook ip.
- Solution: check the output window in Visual Studio and explicitly specify the webhook url:
- Local firewall on Mac blocks connection.
- Solution: add a firewall exception for tcp port 9867
- Firewall blocks traffic (if Windows PC and Mac/iPhone are on different networks).
- Solution: add a firewall exception for tcp port 9867
Quacked: "couldn't quack! the evaluation of the declarations in the code package failed: System.IO.FileNotFoundException: Could not load the file 'netstandard'..."
If the console displays this error then check your iOS Build settings and set the option Linker Behaviour to Don't Link
:
Design
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
deserializes PortaCode
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.