F# Fabulous Xamarin:外部事件订阅
F# Fabulous Xamarin: external event subscription
我是 Fabulous 和 MUV 模型的新手,我正在尝试实现适用于 BLE 的应用程序。我对 F# 也有点陌生,过去主要使用 erlang 和 C#,所以对外部事件处理有点迷茫。 CrossBluetoothLE.Current.Adapter 具有 DeviceDiscovered 事件处理程序 (IEvent)。将此事件处理程序链接到 Fabulous 更新函数的最正确方法是什么?
例如在调用 CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync() 之后,我希望此事件处理程序将新发现的设备提供给更新功能。
如果我做这样的事情(这行不通):
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
如果有效,则可以发现设备,因为 CrossBluetoothLE.Current.Adapter 是静态的。然而,在发现设备后,我将需要处理(例如接收通知或回复),并且不可能将动态设备处理程序包含到 Program.withSubscription.
不确定Fabulous在这里是否适用。
好的,我找到了一些解决方案并且现在可以使用了,但是整体架构看起来有点奇怪。所以一般的方法是创建一个外部邮箱,它将消息发送到 MUV 循环。
- 描述外部模块中MUV的所有消息,例如:
type Msg =
| Scan
| Discovered of IDevice
| Connect of IDevice
| ClockMsg of System.DateTime
| TextMsg of string
- 创建封装邮箱的类型:
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
此处,邮箱以 NotInitialized 状态启动,等待应用程序启动。当一切都完成后,邮箱收到派发功能,将用于进一步派发外部消息到MUV主循环。
- 将调度处理程序传递到邮箱:
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
所以现在,如果你想从外部线程向 MUV 发送事件,只需在 initThreads
内启动它(或者,例如,从 MUV 循环内)并使用类似的东西:Postbox.post (Postbox.Msg (Msgs.TextMsg "It works!"))
。
例如出于我的目的(BLE 发现),它将如下所示:
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
这肯定是一个非常丑陋的解决方案,但我无法想象更漂亮的东西:(。
我是 Fabulous 和 MUV 模型的新手,我正在尝试实现适用于 BLE 的应用程序。我对 F# 也有点陌生,过去主要使用 erlang 和 C#,所以对外部事件处理有点迷茫。 CrossBluetoothLE.Current.Adapter 具有 DeviceDiscovered 事件处理程序 (IEvent)。将此事件处理程序链接到 Fabulous 更新函数的最正确方法是什么?
例如在调用 CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync() 之后,我希望此事件处理程序将新发现的设备提供给更新功能。
如果我做这样的事情(这行不通):
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
如果有效,则可以发现设备,因为 CrossBluetoothLE.Current.Adapter 是静态的。然而,在发现设备后,我将需要处理(例如接收通知或回复),并且不可能将动态设备处理程序包含到 Program.withSubscription.
不确定Fabulous在这里是否适用。
好的,我找到了一些解决方案并且现在可以使用了,但是整体架构看起来有点奇怪。所以一般的方法是创建一个外部邮箱,它将消息发送到 MUV 循环。
- 描述外部模块中MUV的所有消息,例如:
type Msg =
| Scan
| Discovered of IDevice
| Connect of IDevice
| ClockMsg of System.DateTime
| TextMsg of string
- 创建封装邮箱的类型:
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
此处,邮箱以 NotInitialized 状态启动,等待应用程序启动。当一切都完成后,邮箱收到派发功能,将用于进一步派发外部消息到MUV主循环。
- 将调度处理程序传递到邮箱:
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
所以现在,如果你想从外部线程向 MUV 发送事件,只需在 initThreads
内启动它(或者,例如,从 MUV 循环内)并使用类似的东西:Postbox.post (Postbox.Msg (Msgs.TextMsg "It works!"))
。
例如出于我的目的(BLE 发现),它将如下所示:
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
这肯定是一个非常丑陋的解决方案,但我无法想象更漂亮的东西:(。