0

I'm new to Fabulous and MUV model, and I'm trying to implement application that works with BLE. I'm also a bit new to F#, mostly worked with erlang and C# in the past, so a bit lost with external events processing. CrossBluetoothLE.Current.Adapter has DeviceDiscovered event handler (IEvent). What's the most correct way of linking this event handler to the Fabulous update function?

E.g. after I will call CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync(), I want that this event handler supply newly discovered devices to the update function.

And if I will do something like this (this is not working):

type MyApp () as app = 
    inherit Application ()

    let deviceDiscovered dispatch = 
        CrossBluetoothLE.Current.Adapter.DeviceDiscovered.Subscribe (fun x -> dispatch (App.Msg.Discovered x.Device) )

    let runner =
        App.program
        |> Program.withConsoleTrace
        |> Program.withSubscription (fun _ -> Cmd.ofSub deviceDiscovered)
        |> XamarinFormsProgram.run app

if it works, it will be ok for device discovery because CrossBluetoothLE.Current.Adapter is static. However after device will be discovered, I will need to work with (e.g. receive notifications or replies from it), and it will not be possible to include dynamic device handler into Program.withSubscription.

Not sure whether the Fabulous is applicable here.

GregC
  • 7,737
  • 2
  • 53
  • 67
Darkkey
  • 195
  • 7

1 Answers1

0

Ok, I was able to find some solution and it works now, but the overall architecture looks a bit weird. So generic approach is to create an external mailbox, that will dispatch messages to the MUV loop.

  1. Describe all messages of the MUV in the external module, e.g.:
    type Msg = 
        | Scan
        | Discovered of IDevice
        | Connect of IDevice
        | ClockMsg of System.DateTime
        | TextMsg of string
  1. Create type that encapsulates mailbox:
    type DispatchFunc = Msgs.Msg -> unit

    type State = 
        | Initialized of DispatchFunc
        | NotInitialized

    type Mail = 
        | Dispatch of DispatchFunc
        | Msg of Msgs.Msg
        | None

    let rand = System.Random()
    let id = rand.NextDouble()

    let postbox = MailboxProcessor.Start(fun inbox -> 
        let rec messageLoop (state:State) = async{
            let! mail = inbox.Receive()

            let new_state = 
                match mail with 
                | None ->
                    state
                | Msg msg -> 
                    match state with 
                    | NotInitialized -> NotInitialized
                    | Initialized df ->
                        df msg
                        state
                | Dispatch df -> 
                    Initialized df

            return! messageLoop (new_state)
            }

        messageLoop (NotInitialized))

    let post(o) =
        postbox.Post o

Here, mailbox starts with NotInitialized state and wait while application will start. When everything is done, mailbox received dispatch function, that will be used in further dispatching of the external messages to the MUV main loop.

  1. Pass dispatch handler to the mailbox:
type MyApp () as app = 
    inherit Application ()

    // generate initial events + start threads + pass dispatch reference to the mailbox
    let initThreads dispatch =
        // init & start external (e.g. bluetooth receiver) threads here
        // or start them asynchronously from MUV loop
        Postbox.post (Postbox.Dispatch dispatch)
        ()

    let runner = 
        App.program
        |> Program.withConsoleTrace
        |> Program.withSubscription (fun _ -> Cmd.ofSub initThreads)  
        |> XamarinFormsProgram.run app

So now, if you want to send event to the MUV from external thread, just start it inside initThreads (or, e.g. from within MUV loop) and use something like: Postbox.post (Postbox.Msg (Msgs.TextMsg "It works!")).

E.g. for my purposes (BLE discovery) it will look like this:

    let update msg model =
        match msg with
        | Msgs.Scan -> 
            CrossBluetoothLE.Current.Adapter.StopScanningForDevicesAsync() |> Async.AwaitTask |> ignore
            CrossBluetoothLE.Current.Adapter.DeviceDiscovered.Subscribe (
                    fun (a) ->
                        Postbox.post (Postbox.Msg (Msgs.Discovered a.Device))
                        ()
                ) |> ignore
            CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync() |> Async.AwaitTask |> ignore
            model, Cmd.none
        | Msgs.ClockMsg msg ->
            { model with label = msg.ToString() }, Cmd.none
        | Msgs.TextMsg msg ->
            { model with label = msg }, Cmd.none
        | Msgs.Discovered d ->
            { model with gattDevices = d::model.gattDevices; label = "Discovered " + d.ToString() }, Cmd.none
        | Msgs.Connect d -> { model with connectedDevice = d }, Cmd.none

This is for sure a very ugly solution, but I wasn't able to imagine something more beautiful :(.

Darkkey
  • 195
  • 7