Deprecated. Use Mu.
Inspired by TEA (The Elm Architecture) but for Xamarin stack, in an unobtrusive way.
To simplify, an UI component can split into those three parts:
type State () =
member val Count = 0 with get, set
To tell component how to update state, we will dispatch message Msg
. Notice you should use the builtin State.update
to update state but not assign directly.
type Msg =
| Increment
| Decrement
let update msg (state: State) =
match msg with
| Increment ->
State.update <@ state.Count @> (state.Count + 1)
| Decrement ->
State.update <@ state.Count @> (state.Count - 1)
Bind state to UI, and setup message dispatching for UI controls. And, for convenience, you should use State.bind
for UI binding.
interface IView'<State, Msg> with
member x.BindState state =
State.bind <@ state.Count @> <| fun count ->
x.countLabel.Text <- sprintf "state.Count: %d" count
member x.Subscribe state dispatch =
x.incrButton.TouchUpInside.Add <| fun _ -> dispatch Increment
x.decrButton.TouchUpInside.Add <| fun _ -> dispatch Decrement
Before we can run the component, we should make State
reactivable by implementating State.I
interfaces. Fortunately, we can simply by subclassing State.Base
:
type State () =
inherit State.Base ()
member val Count = 0 with get, set
To eleminate State
type annotation (since it is a class not record), you can use Component.make''
to setup the component:
let make () = Component.make'' {
init = fun () ->
State ()
update = fun msg state ->
match msg with
| Increment ->
State.update <@ state.Count @> (state.Count + 1)
| Decrement ->
State.update <@ state.Count @> (state.Count - 1)
}
Run when view ready (on iOS viewDidLoad
or Android OnCreate
).
CounterComponent.make () |> Component.runWithView'' viewInstance
Check the SimpleCounter example for detail.
When returning a command Cmd<'msg>
instead of unit
, HalfTea
will execute the command:
...
| Msg.RequestRandomImage ->
State.update <@ state.RequstMessage @> "Requesting random image..."
let uri = Uri "https://picsum.photos/200/100/?random"
use client = new WebClient ()
Cmd.ofAsync
(fun () -> client.DownloadDataTaskAsync uri |> Async.AwaitTask)
(Choice1Of2 >> Msg.Response)
(Choice2Of2 >> Msg.Response)
...
We offer some utils to make a command to perform async task or function, check out the Cmd
command.
With command you ask to do something, but in the real world, something is out of our control, just like a clock, all we can do is to listen its tick, this is where subscribe
come in.
subscribe = fun _state dispatch ->
let t = new Timers.Timer 1.
t.Elapsed.Subscribe (fun arg -> dispatch <| Msg.Tick arg.SignalTime) |> ignore
t.Start ()
Check the Counter example for detail.
Q: Why mutable class but not immutable record for state?
A: Because we need INotifyPropertyChanged
to inform UI to update. And for the OO-based UI framework (Xamarin.iOS, Xamarin.Droid and Xamarin.Forms), mutable class state is much more fitted.
Drag src/HalfTea.fs
to your project. To track version, you may use paket or git submodules.