fsprojects/Elmish.XamarinForms

Name: Elmish.XamarinForms

Owner: F# Community Project Incubation Space

Description: Elmish for Xamarin.Forms

Created: 2018-02-05 17:00:17.0

Updated: 2018-05-23 09:30:01.0

Pushed: 2018-05-23 16:24:14.0

Homepage: https://github.com/fsprojects/Elmish.XamarinForms/blob/master/README.md

Size: 2261

Language: F#

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

F# Functional App Development, using Xamarin.Forms

NuGet version Build status (Windows) Build status (OSX) Gitter chat

Never write a ViewModel class again! Conquer the world with clean dynamic UIs!

This library uses a variation of elmish, an Elm architecture implemented in F#, to build Xamarin.Forms applications. Elmish was originally written for Fable applications, however it is used here for mobile applications using Xamarin.Forms. This is a sample and may change.

To quote @dsyme:

In my work for Xamarin, I'm asking myself “what will appeal to F# devs who want to do Xamarin programming?“. These devs are very code-oriented and know F#. People are liking Elm and React via Elmish and also React Native. Can we apply some of the lessons to Xamarin programming?

Getting started with Elmish.XamarinForms
  1. Create a blank F# Xamarin Forms app in Visual Studio or Visual Studio Code. Make sure your shared code is an F# .NET Standard 2.0 Library project.
  2. Add nuget package Elmish.XamarinForms to to your shared code project.
  3. Put the sample code below in your shared app library
A Basic Example

Here is a full example of an app:

The messages dispatched by the view
 Msg =
| Pressed

The model from which the view is generated
 Model = 
{ Pressed: bool }

Returns the initial state
init() = { Pressed=false }

The funtion to update the view
update (msg:Msg) (model:Model) =
match msg with
| Pressed -> { model with Pressed = true }

The view function giving updated content for the page
view (model: Model) dispatch =
if model.Pressed then 
    Xaml.Label(text="I was pressed!")
else
    Xaml.Button(text="Press Me!", command=(fun () -> dispatch Pressed))

 App () = 
inherit Application ()

let runner = 
    Program.mkSimple init update view
    |> Program.withConsoleTrace
    |> Program.withDynamicView
    |> Program.run

The init function returns your initial state, and each model gets an update function for message processing. The view function computes an immutable Xaml-like description. In the above example, the choice between a label and button depends on the model.Pressed value.

Some advantages of using an immutable model are:

Example Views

The sample CounterApp contains a slightly larger example of Button/Label/Slider controls.

The sample AllControls contains examples of instantiating most controls in Xamarin.Forms.Core.

Screenshots from Anrdoid (Google Pixel):

Dynamic Views

Dynamic view functions are written using an F# DSL, see Elmish.XamarinForms.DynamicViews. Dynamic Views excel in cases where the existence, characteristics and layout of the view depends on information in the model. React-style differential update is used to update the Xamarin.Forms display based on the previous and current view descriptions.

Notes:

Dynamic Views: Performance Hints

Dynamic views are only efficient for large UIs if the unchanging parts of a UI are “memoized”, returning identical objects on each invocation of the view function. This must be done explicitly, currently using dependsOn. Here is an example for a 6x6 Grid that depends on nothing, i.e. never changes:

view model dispatch =
...
dependsOn () (fun model () -> 
    Xaml.StackLayout(
      children=
        [ Xaml.Label(text=sprintf "Grid (6x6, auto):")
          Xaml.Grid(rowdefs= [for i in 1 .. 6 -> box "auto"],
                    coldefs=[for i in 1 .. 6 -> box "auto"], 
                    children = [ for i in 1 .. 6 do for j in 1 .. 6 -> 
                                   Xaml.BoxView(Color((1.0/float i), (1.0/float j), (1.0/float (i+j)), 1.0) )
                                          .GridRow(i-1).GridColumn(j-1) ] )
        ])

Inside the function - the one passed to dependsOn - the model is rebound to be inaccessbile with a DoNotUseMe type so you can't use it. Here is an example where some of the model is extracted:

view model dispatch =
...
dependsOn (model.CountForSlider, model.StepForSlider) (fun model (count, step) -> 
    Xaml.Slider(minimum=0.0, maximum=10.0, value= double step, 
                valueChanged=(fun args -> dispatch (SliderValueChanged (int (args.NewValue + 0.5)))), 
                horizontalOptions=LayoutOptions.Fill)) 
...

In the example, we extract properties CountForSlider and StepForSlider from the model, and bind them to count and step. If either of these change, the section of the view will be recomputed and no adjustments will be made to the UI. If not, this section of the view will be reused. This helps ensure that this part of the view description only depends on the parts of the model extracted.

You can also use

Dynamic Views: Resource Dictionaries

In Elmish.XamarinForms, resources dictionaries are just “simple F# programming”, e.g.

horzOptions = LayoutOptions.Center
vertOptions = LayoutOptions.CenterAndExpand

is basically the eqivalent of Xaml:

tentPage.Resources>
<ResourceDictionary>
    <LayoutOptions x:Key="horzOptions"
                 Alignment="Center" />

    <LayoutOptions x:Key="vertOptions"
                 Alignment="Center"
                 Expands="True" />
</ResourceDictionary>
ntentPage.Resources>

In other words, you can normally forget about resource dictionaries and just program as you would normally in F#.

Other kinds of resources like images need a little more attention and you may need to ship multiple versions of images etc. for Android and iOS. TBD: write a guide on these, in the meantime see the samples.

Dynamic Views: Multiple Pages and Navigation

Multiple pages are just generated as part of the overall view.

Four multi-page navigation models are shown in AllControls:

NavigationPage push/pop

The basic principles are easy:

  1. Keep some information in your model indicating the page stack (e.g. a list of page identifiers or page models)
  2. Return the current visual page stack in the pages property of NavigationPage.
  3. Set HasNavigationBar and HasBackButton on each sub-page according to your desire
  4. Dispatch messages in order to navigate, where the corresponding update adjusts the page stack in the model
view model dispatch = 
Xaml.NavigationPage(pages=
    [ for page in model.PageStack do
        match page with 
        | "Home" -> 
            yield Xaml.ContentPage(...).HasNavigationBar(true).HasBackButton(true)
        | "PageA" -> 
            yield Xaml.ContentPage(...).HasNavigationBar(true).HasBackButton(true)
        | "PageB" -> 
            yield Xaml.ContentPage(...).HasNavigationBar(true).HasBackButton(true)
    ])
TabbedPage navigation

Return a TabbedPage from your view:

view model dispatch = 
Xaml.TabbedPage(children= [ ... ])
CarouselPage navigation

Return a CarouselPage from your view:

view model dispatch = 
Xaml.CarouselPage(children= [ ... ])
MasterDetail Page navigation

Principles:

  1. Keep some information in your model indicating the current detail being shown
  2. Return a MasterDetailPage from your view function

See the AllControls sample

Dynamic Views: ListView, ListGroupedView and friends

The programming model supports several more bespoke uses of the underlying ListView control from Xamarin.Forms.Core. In the simplest form, just called ListView, the items property specifies a collection of visual elements:

Xaml.ListView(items = [ Xaml.Label "Ionide"
                        Xaml.Label "Visual Studio"
                        Xaml.Label "Emacs"
                        Xaml.Label "Visual Studio Code"
                        Xaml.Label "JetBrains Rider"], 
              itemSelected=(fun idx -> dispatch (ListViewSelectedItemChanged idx)))

In the underlying implementation, each visual item is placed in a ContentCell. Currently the itemSelected callback uses integers indexes for keys to identify the elements (NOTE: this may change in future updates).

There is also a ListViewGrouped for grouped items of data.

“Infinite” or “unbounded” ListViews

“Infinite” (really “unbounded”) lists are created by using the itemAppearing event to prompt a message which nudges the underlying model in a direction that will then supply new items to the view.

For example, consider this pattern:

 Model = 
{ ...
  LatestItemAvailable: int 
}

 Message = 
...
| GetMoreItems of int

update msg model = 
match msg with 
| ...
| GetMoreItems n -> { model with LatestItemAvailable = n }

view model dispatch = 
...
Xaml.ListView(items = [ for i in 1 .. model.LatestItemAvailable do 
                          yield Xaml.Label("Item " + string i) ], 
              itemAppearing=(fun idx -> if idx >= max - 2 then dispatch (GetMoreItems (idx + 10) ) )  )
...

Note:

Surprisingly even this naive technique is fairly efficient. There are numerous ways to make this more efficient (we aim to document more of these over time too). One simple one is to memoize each individual visual item using dependsOn:

              items = [ for i in 1 .. model.LatestItemAvailable do 
                          yield dependsOn i (fun model i -> Xaml.Label("Item " + string i)) ]

With that, this simple list views scale to > 10,000 items on a modern phone, though your mileage may vary. There are many other techniques (e.g. save the latest collection of visual element descriptions in the model, or to use a ConditionalWeakTable to associate it with the latest model). We will document further techniques in due course.

Thre is also an itemDisappearing event for ListView that can be used to discard data from the underlying model and restrict the range of visual items that need to be generated.

Dynamic Views: Differential Update of Lists of Things

There are a few different kinds of list in view descriptions:

  1. lists of raw data (e.g. data for a chart control, though there are no samples like that yet in this library)
  2. long lists of UI elements that are used to produce cells (e.g. ListView, see above)
  3. short lists of UI elements (e.g. StackLayout children)
  4. short lists of pages (e.g. NavigationPages pages)

The perf of incremental update to these is progressively less important as you go down that list above.

For all of the above, the typical, naive implementation of the view function returns a new list instance on each invocation. The incremental update of dynamic views maintains a corresponding mutable target (e.g. the Children property of a Xamarin.Forms.StackLayout, or an ObservableCollection to use as an ItemsSource to a ListView) based on the previous (PREV) list and the new (NEW) list. The list diffing currently does the following:

  1. trims of excess elements from TARGET down to size LIM = min(NEW.Count, PREV.Count)
  2. incrementally updates existing elements 0..MIN-1 in TARGET (skips this if PREV.[i] is reference-equal to NEW.[i])
  3. creates elements LIM..NEW.Count-1

This means

  1. Incremental update costs minimally one transition of the whole list.
  2. Incremental update recycles visual elements at the start of the list and handles add/remove at end of list relatively efficiently
  3. Returning a new list that inserts an element at the beginning will recreate all elements down the way.

Basically, incremental update is faster if lists are changing at their beginning, rather than their end.

The above is sufficient for many purposes, but care must always be taken with large lists and data sources, see ListView above for example. Care must also be taken whenever data updates very rapidly.

Dynamic Views: Styling

Styling is a significant topic in Xamarin.Forms programming. See the extensive Xamarin.Forms documentation on styling.

F#-coded styling

One approach is to manually code up styling simply by using normal F# programming to abstract away commonality between various parts of your view logiv.

We do not give a guide here as it is routine application of F# coding. The Fulma approach to styling may also be of interest and provide inspiration.

There are many upsides to this approach. The downsides are:

CSS styling with Xamarin.Forms 3.0
  1. create a CSS file with appropriate selectors and property specifications, e.g.
    klayout {
    in: 20;
    
    

.mainPageTitle {

font-style: bold;
font-size: medium;

}

.detailPageTitle {

font-style: bold;
font-size: medium;
text-align: center;

}

e `stacklayout` referes to all elements of that type, and `.mainPageTitle` refers to a specific element style-class path. 

dd the style sheet to your app as an `EmbeddedResource` node

oad it into your app:

type App () as app =

inherit Application ()
do app.Resources.Add(StyleSheet.FromAssemblyResource(Assembly.GetExecutingAssembly(),"MyProject.Assets.styles.css"))
et `StyleClass` for named elements, e.g. 
  Xaml.Label(text="Hello", styleClass=detailPageTitle")
  ...
  Xaml.Label(text="Main Page", styleClass="mainPageTitle")
 "Xaml" coding via explicit `Style` objects

can also use "Xaml styling" by creating specific `Style` objects using the `Xamarin.Forms` APIs directly
attaching them to your application.  See [the Xamarin.Forms documentation](https://docs.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/styles/xaml/).  We don't go into details here
odels

Models: Messages and Validation

dation is generally done on updates to the model, storing error messages from validation logic in the model
hey can be correctly and simply displayed to the user.  Here is an example of a typical pattern.
type Temperature = 
   | Value of double
   | ParseError of string  

type Model = 
    { TempF: Temperature
      TempC: Temperature }

/// Validate a temperature in Farenheit, can be shared between client/server
let validateF text =  ... // return a Result

/// Validate a temperature in celcius, can be shared between client/server
let validateC text = // return a Result 

let update msg model =
    match msg with
    | SetF textF -> 
        match validateF textF with
        | Ok newF -> { model with TempF = Value newF }
        | Error msg -> { model with TempF = ParseError msg }

    | SetC textC -> 
        match validateC textC with
        | Ok newC -> { model with TempC = Value newC }
        | Error msg -> { model with TempC = ParseError msg }
 that the same validation logic can be used in both your app and a service back-end.

Models: Saving Application State

ication state is very simple to save by serializing the model into `app.Properties`. For example, you can store as JSON as follows using [`FsPickler` and `FsPickler.Json`](https://github.com/mbraceproject/FsPickler), which use `Json.NET`:

open MBrace.FsPickler.Json

type Application() =

....
let modelId = "model"
override __.OnSleep() = 
    app.Properties.[modelId] <- FsPickler.CreateJsonSerializer().PickleToString(runner.Model)

override __.OnResume() = 
    try 
        match app.Properties.TryGetValue modelId with
        | true, (:? string as json) -> 
            runner.SetCurrentModel(FsPickler.CreateJsonSerializer().UnPickleOfString(json), Cmd.none)
        | _ -> ()
    with ex -> 
        program.onError("Error while restoring model found in app.Properties", ex)

override this.OnStart() = this.OnResume()
essages, Commands and Control

Messages, Commands and Asynchronous Actions

chronous actions are triggered by having the `update` function return "commands", which can trigger later `dispatch` of further messages.

ange `Program.mkSimple` to `Program.mkProgram`
let program = Program.mkProgram App.init App.update App.view
ange your `update` function to return a pair of a model and a command. For most messages the command will be `Cmd.none` but for basic async actions use `Cmd.ofAsyncMsg`.

example, here is one pattern for a timer loop that can be turned on/off:
type Model = 
    { ...
      TimerOn: bool 
    }

type Message = 
    | ...
    | TimedTick
    | TimerToggled of bool

let timerCmd = 
    async { do! Async.Sleep 200
            return TimedTick }
    |> Cmd.ofAsyncMsg

let update msg model =
    match msg with
    | ...
    | 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
state-resurrection `OnResume` logic of your application (see above) should also be adjusted to restart
opriate `async` actions accoring to the state of the application.

: Making all stages of async computations (and their outcomes, e.g. cancellation and/or exceptions) explicit can add
tional messages and model states. This comes with pros and cons. Please discuss concrete examples by opening issues
his repository.

Messages: Global asynchronous event subscriptions

can also set up global subscriptions, which are events sent from outside the view or the dispatch loop. For example, dispatching `ClockMsg` messages on a global timer:
let timerTick dispatch =
    let timer = new System.Timers.Timer(1.0)
    timer.Elapsed.Subscribe (fun _ -> dispatch (ClockMsg System.DateTime.Now)) |> ignore
    timer.Enabled <- true
    timer.Start()

...
let runner = 
    ...
    |> Program.withSubscription timerTick
    ...
tatic Views and "Half Elmish"

ome circumstances there are advantages to using static Xaml, and static bindings from the model to those views. This is called "Half Elmish" and is the primary technique used by [`Elmish.WPF`](https://github.com/Prolucid/Elmish.WPF) at time of writing. (It was also  the original technique used by this repo and the prototype `Elmish.Forms`).   

[HALF-ELMISH.md](HALF-ELMISH.md)

oadmap

[ROADMAP.md](https://github.com/fsprojects/Elmish.XamarinForms/blob/master/ROADMAP.md), a list of TODOs.

uilding

[DEVGUIDE.md](DEVGUIDE.md).

ontributing

se contribute to this library through issue reports, pull requests, code reviews and discussion.


its
-
 library is inspired by [Elmish.WPF](https://github.com/Prolucid/Elmish.WPF), [Elmish.Forms](https://github.com/dboris/elmish-forms) and [elmish](https://github.com/elmish/elmish), written by [et1975](https://github.com/et1975). This project technically has no tie to [Fable](http://fable.io/), which is an F# to JavaScript transpiler that is definitely worth checking out.

This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.