praeclarum/ImmutableUI

Looking at F# code generator based on this

dsyme opened this issue · 18 comments

dsyme commented

Hi @praeclarum

Just to let you know that I'm mucking about with adapting your code to be an F# code generator for use with Elmish.XamarinForms projects. As you may have seen I played around with a Elmish-style view function, see here and here.

The resulting code in my very early draft is different to that which results from your API since it uses F#-defined function values. But nothing is set in stone and we could change things, we need to experiment.

Initially I'll just adapt your code generator to generate the same API but in F# code, and then see if there are ways to make the API as idiomatic as possible for F# programming. Depending on where you take this I'll try to keep it aligned.

Wow, super cool!

I nearly switched the generator myself to use F# record types (to get the free equality function) but I couldn't figure out how to model inheritance. If I switched inheritance to composition, I worried how the view Children collections would work. I can't wait to see your solution.

I should note that there are a few big features missing from this that I plan on adding.

  1. For event handling, I was planning on just adding the delegate as a property.

  2. For caching, I was going to do something more complex - I would emit Views that contain the ViewModel that created them. Then, when I have to update, I can diff against that stored state to do precise updates.

    Technically, I could pull off the caching by interrogating the mutable objects but most of them do that already. The big benefit would come from collections and avoiding walking whole parts of the tree.

  3. Collections. I need to implement RelativeLayout, FlexLayout, and AbsoluteLayout's Children property. This is gonna take some adhoc code since the API is a little weird. But I will also need a solution for UIKit that can handle these scenarios so I was planning on putting in the time.

dsyme commented

Hey do you have a plan for Grid?

dsyme commented

For event handling, I was planning on just adding the delegate as a property.

Yes

For caching, I was going to do something more complex - I would emit Views that contain the ViewModel that created them. Then, when I have to update, I can diff against that stored state to do precise updates. Technically, I could pull off the caching by interrogating the mutable objects but most of them do that already. The big benefit would come from collections and avoiding walking whole parts of the tree.

This sounds right

I have two options for Grid, RelativeLayout, FlexLayout, and AbsoluteLayout:

  1. My first plan was to add all the dependency properties to View itself. So ViewModel would contain GridRow, GridRowSpan, etc. properties. I like this because the API is simple and application to the actual grid will be easy. Downside is the class namespace pollution (talking about 8 extra variables?). I don't like all these extras added.

  2. My second option was to wrap all the children in layout specific wrappers. So I would have new GridChild (view: myView, gridRow: 3) and new AbsoluteChild (view: myView, x: 100, y: 200). This is a cleaner solution.

It boils down to which syntax you like more:

var grid = new Grid (children: new[] {
    view1.WithGridRow (0).WithGridColumn (0),
    view2.WithGridRow (1).WithGridColumn (1),
});

or

var grid = new Grid (children: new[] {
    new GridChild (view1, row: 0, column: 0),
    new GridChild (view2, row: 1, column: 1)
});

In the beginning I was preferring the first, but I think I'm coming around to the second. Which do you prefer?

dsyme commented

@praeclarum I'm oscillating fairly wildly between different potential styles for F#. Currently headed more towards your second.

It's interesting to me that Fable view descriptions appear quite successful and intuitive despite having considerably less strong typing. I need to think about that

dsyme commented

@praeclarum I ended up with this generated API so far for F#: https://github.com/fsprojects/Elmish.XamarinForms/blob/master/Elmish.XamarinForms/DynamicXaml.fs

Summary

Example use is either

depending on how many extra F# helpers we have and the approach we take to implicit conversions.

Amazing work! I still need to read through your implementation, but I love how the UI construction code turned out. The grid syntax in particular is great.

button |> withText "Hello" seemed a little odd to me but after reading through your samples, it ends up looking decent.

I avoided Map<string, object> for my properties, but I'm pretty jealous of how well it turned out for you. It certainly simplifies the problem of knowing what's set and diffing.

For updating views with multiple children, I was going to implement a standard list differ. (Like https://www.nuget.org/packages/ListDiff/). I use this class to great effect in my own apps to keep UI updates smooth. Also, on iOS, you can animate so it's important to do high-level operations on lists (RemoveRange, AddRange, etc) and a proper diff can provide that info.

I will read through your incremental code and see if I can spot the flicker cause. Which sample shows it worst?

A little more on collections: I think ListView should just have an immutable list of Cell-typed Items instead of ItemsSource/DataTemplate. If that list is in the model, then the diff I described above can be used and you can do smooth updates when the data changes. This was yet another feature I didn't get to.

dsyme commented

A little more on collections: I think ListView should just have an immutable list of Cell-typed Items instead of ItemsSource/DataTemplate. If that list is in the model, then the diff I described above can be used and you can do smooth updates when the data changes. This was yet another feature I didn't get to.

Yes, totally right. I'll also take a look at ListDiff and have a think about that.

I'm not sold on the button |> withText "foo" but it's an interesting option and is a little more Fable-like than the Xaml.Button(text="foo").

I'll bundle up the code generator into the Elmish.XamarinForms repo tomorrow and work on converting further samples. Would love your help in clarifying what needs to be done to make this truly usable, and in taking it for a usability/perf spin. Also need to think of a testing strategy. The LivePlayer side of things is also totally crucial.

dsyme commented

A little more on collections: I think ListView should just have an immutable list of Cell-typed Items instead of ItemsSource/DataTemplate. If that list is in the model, then the diff I described above can be used and you can do smooth updates when the data changes. This was yet another feature I didn't get to.

What's the right way to create such a thing once you have the Cells? Set the ItemTemplate to basically the identity function? And will this scale for very large lists where you do a partial view of the list?

I was planning on setting an observable collection to the ItemsSource that mirrors the cell list. When the cell list changes, update the observable collection with these Cells. Then set the DataTemplate to handle the conversion of the CellView objects to real Cells (either by calling Create or Apply). I have to review the API to make sure this plays nice with cell reuse.

It's efficient because we're still only creating real cells when they need to be displayed. I would review the iOS ListViewRenderer to verify, but this should be plenty fast. Also mobile apps don't display lists of millions of items - its just bad/useless UI - so we have that helping. :-)

dsyme commented

Re flicker - it seems to be better now. I noticed it on some moves in tic-tac-toe, which replaces grid items. By all means review the code though pleeeease.

I made a lot of progress over the last few days. Events are wired up. See CounterApp too. Also starting to work on systematically using all available xamarin controls, see AllControls. Having trouble with resources, not sure why.

Only really SPA right now, pages are not yet part of the system.

dsyme commented

See https://github.com/fsprojects/Elmish.XamarinForms/blob/master/Samples/AllControls/AllControls/AllControls.fs

I'm preferring the Xaml.Button syntax right now. A very direct translation from Xaml.

dsyme commented

@praeclarum Re this:

Then set the DataTemplate to handle the conversion of the CellView objects to real Cells (either by calling Create or Apply).

Is this allowed? AFAICS the DataTemplate must return a ViewCell that in turn uses binding through to the item. And AFAICS the view property of ViewCell is not a bindable property.

I don't yet see any way to make DataTemplate a simple generator of cells....

I must say the whole DataTemplate design makes me physically ill.

I’m not sure why you care about databinding since this approach doesn’t use XAML.

dsyme commented

@praeclarum I managed to solve this for ViewCell by using a custom cell type, see https://github.com/fsprojects/Elmish.XamarinForms/blob/master/Elmish.XamarinForms/DynamicXamlConverters.fs#L42

There are no Bindings, but you do have to have a type that reacts to OnBindingContextChanged since that is how the cells created by the template are associated with their element.

dsyme commented

@praeclarum I assume we could use a similar technique for EntryCell etc. but I haven't done that yet.

dsyme commented

I'm running through examples of every Xamarin.Forms.Core control bit by bit: https://github.com/fsprojects/Elmish.XamarinForms/blob/master/Samples/AllControls/AllControls/AllControls.fs#L96

Let me know what you think