Layout Serialization
So I have an app where the layout is customizable, and it would be very nice to be able to save the layout between sessions. However, for the life of me, I can't figure out a way to do this with the way FuncUI works.
The problem is that it's very difficult to serialize higher order functions in any sort of way. I'd prefer to be able to do this without use of System.Reflection.Emit (so FSPickler is out) or the dynamic keyword, since certain platforms do not support this. I've tried an inheritance approach, to sidestep the problem, like
[<DataContract>]
type IControl =
abstract GetUpdateFunc: unit -> ('msg -> 'model -> 'model)
abstract GetViewFunc: unit -> ('model -> 'dispatch -> 'view)
but the moment you try to implement such an interface, the F# compiler gives type errors with the generics. As far as I can tell, F# generic functions are fundamentally incompatible with the .NET type inheritance system. Trying to use the dictionary types to make a registry will run into the same problem. Maybe it's possible to use marker interfaces instead of generics? Of course, it's preferred that 'model is a record, which does not support inheritance. Maybe reflection abuse? Of course, at this point I may as well just cast everything to obj...
Is there an accepted common way of doing this?
If I understand you right, you need to save your state across application launches. If you need to save layout in terms of docking, I can't help here.
I'm using System.Text.Json + FSharp.SystemTextJson to accomplish live update through Live.Avalonia. Idea is to serialize entire state graph into temporary storage and read it again on launch.
Introduce property State in MainWindow:
type MainControl(state) as this =
inherit HostWindow() // or HostControl is my case
let mutable _state : MainView.State = state
do
//loop initalization
new () = MainControl(Unchecked.defaultof<_>) // create default constructor
member _.State
with get () = _state // have to use manual properties because we need to access _state in constructor
and set v = _state <- v
Modify program creation:
Elmish.Program.mkProgram
(fun () ->
if isNull (box _state) then // if we don't have state, create default
MainView.init()
else
_state, Cmd.none) // use existing. Maybe send ViewRestoredCmd to handle restoration (implement it yourself)
(fun msg state ->
let newState, cmd = MainView.update msg state // save state after each update
_state <- newState
newState, cmd)
MainView.view
Finally you need to create this control at startup. (I've copy-pasted it from hot-reload)
interface ILiveView with
member _.CreateView(window: Window) =
match window.Content with
| null -> MainControl()
| o ->
let t = o.GetType()
match t with
| null -> MainControl()
| _ ->
let p = t.GetProperty("State")
match p with
| null -> MainControl()
| _ ->
let obj = p.GetValue(o)
match obj with
| null -> MainControl()
| _ ->
let opt = JsonSerializerOptions()
opt.Converters.Add(JsonFSharpConverter())
let s = JsonSerializer.Serialize(obj, opt)
let c = JsonSerializer.Deserialize(s, opt)
MainControl(c)
|> box
Problem here is complex types, which cannot be serialized, e.g. IBitmap, so we have to mark properties with them as System.Text.Json.Serialization.JsonIgnoreAttribute and handle recreation manually.
Hope it will help you in further investigations!
This is half the solution! The problem I have is that I don't know what type of control I will be using in advance. I guess what I'm trying to figure out how to do is create a dynamic viewmodel layer that allows a top level layout (mostly) independent of the model, and can persist it between sessions. The sticking point is when, during deserialization, the viewmodel needs to say "create control XXX that is bound to this bit of the model". It's proving remarkably hard to figure out an API for this.
Closing as this issue appears to be abandoned.