question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Experiments: Stronger typing, Mixing Dynamic/Static views, Componentization, SwiftUI-like DSL

See original GitHub issue

DISCLAIMER: All the following points are experiments only and there’s no guarantee they will be available anytime in the future.

Index:

In the last few days, I’ve been experimenting with a couple of ideas.

Introduction

  • Stronger view typing (#341)

Instead of having all View.XXX() return untyped ViewElement, it should return a typed ViewElement to make the compiler check for invalid uses (like putting a Button as an item of ListView – ListView only accepts Cell-derivated controls).

  • Diffing per “attribute” instead of per “control”

Currently in Fabulous.XamarinForms, we diff controls by calling the Update method attached to it (e.g. View.Button() will have a link to ViewBuilders.UpdateButton()).

This update method will check each one of the properties of Button (and will call UpdateVisualElement, etc. to update the inherited properties/events). This is perfectly fine in 99% of the cases, except when dealing with overridden properties or attached properties.

This becomes particularly tricking to always apply the correct behavior and it results in a lot of custom code.

Instead if we went with “attribute”-based diffing like FuncUI, overridden properties could simply be dealt with by storing the overridden attribute updater instead of the base one in an attributes array (e.g. ViewElement.Attributes). Same with attached properties, there would be no need to be aware that Grid.Row is an attached property applicable only on the Grid inherited Layout<T>.Children items.

  • Merging Fabulous and Fabulous.StaticViews

Currently you need to choose from either Fabulous or Fabulous.StaticViews to write apps. Mixing the 2 is not possible.

Though there’s instances where being able to use the 2 makes sense. Fabulous.StaticViews makes it easy to use 3rd party controls (or custom XAML views) without spending too much time writing wrappers for it.

It would also be way easier to “market” Fabulous, less confusing on which package to use.

  • Allow Fabulous.XamarinForms to run as a component of an existing app, or as an app as today

Fabulous.XamarinForms currently plugs itself in the Xamarin.Forms.Application.MainPage property and acts from there. It is quite easy to allow it to plug into a ContentView.Content property instead, allowing it to run anywhere, in multiple instances, even in existing C# apps.

  • A SwiftUI-like DSL

I really like the Fabulous DSL, though IDEs make it difficulty to know what’s exactly is available when having like 20 optional method parameters. Elmish.React - Feliz - Avalonia.FuncUI all have great advantages, but also have different issues in my opinion, I will explain why in a comment below.

So I tried various approaches to keep it close to what’s Fabulous DSL offers today, while trying to make it IDE-friendly. After pushing the F# syntax to its limit several times, I went with a SwiftUI-like syntax. I think it offers the best balance between readability and discoverability, as well as being nice with the IDEs.

I added a little Flutter-like widgets to the mix to make it great with static views code.

Current experiments can be found in https://github.com/TimLariviere/Fabulous/tree/play

Read the comments below for more information.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:14
  • Comments:48 (22 by maintainers)

github_iconTop GitHub Comments

16reactions
TimLarivierecommented, May 11, 2020

SwiftUI-like DSL

I must say I was quite in awe when first playing with SwiftUI from Apple. Their IDE-integration was so sweet. The way they declare UI also made a lot of sense to me.

Basically, you have the following:

HStack(alignment = .leading) {
    Label("Hello world")
        .font(.title)
        .color(.grey)

    Image("path/to/image")
}

So to use a control, you have 4 distinct parts.

  • Its name HStack
  • Its most meaningful properties inside the parentheses (either mandatory or optional - default value is fine but there’s a high chance I will change the columns of a Grid for example).
  • Optional child/children
  • Optional additional properties I might change like the color, the padding, etc.

The meaningful properties are not available as additional property methods.

Even though F# doesn’t have the { } syntax for the children, it could be mimicked by [ ]. Here’s a Todo List app using the SwiftUI-like DSL (use F# 4.7 open static classes):

ContentPage(
    StackLayout(spacing = 10.) [
        Label("Fabulous Todo List")
            .font(NamedSize.Title)
            .textColor(Color.Blue)
            .horizontalOptions(LayoutOptions.Center)
            .padding(top = 50., bottom = 20.)
            
        Grid(coldefs = [ Star; Absolute 50 ]) [
            Entry(
                text = model.EntryValue,
                textChanged = fun args -> dispatch (EntryTextChanged args.NewTextValue)
            )
                
            Button("Add", fun () -> dispatch AddTodo)
                .gridColumn(1)
        ]
        
        ListView(model.Todos) (fun item ->
            TextCell(item)
                .contextActions([
                    MenuItem("Delete", fun() -> dispatch (RemoveTodo item))
                ])
        )
    ]
)

The signal-to-noise ratio is pretty good.

You’re also immediately aware of what’s important and what’s not with the most meaningful properties inside the parentheses.

If you use a Label, 100% of the time you’ll want a text set to it. So make it mandatory. Also why specify the field name? e.g. View.Label(text = "Hello World")

If you want a Button, you’ll want to set a content and listen for the click so make it mandatory. If you use a Grid, you’ll most likely set columns and rows. Though you might not necessarily do that because the default values are fine. So optional is good but keep it close to the type.

Optional properties that are less-likely to be used are pushed as external methods. This helps understand the intent, as well as allow overloaded signatures. For instance, a font size could be an absolute value or a named size. This is possible to have both without having a DU.

// Current
Label(text = "Hello", fontSize = Named NamedSize.Title)
Label(text = "Hello", fontSize = FontSize 10.)

// Versus
Label("Hello").font(NamedSize.Title)
Label("Hello").font(10.)

This also allows us to provide helpers for things done often

// Current
Label(text = "Hello", padding = Thickness(10.))
Label(text = "Hello", padding = Thickness(0., 50., 0., 20.))
Label(text = "Hello", padding = Thickness(20., 30.))

// Versus
Label("Hello").padding(10.)
Label("Hello").padding(top = 50., bottom = 20.)
Label("Hello").padding(leftRight = 20., topBottom = 30.)
Label("Hello").padding() // Could even have a default padding values (e.g. 5px all around)

Controls can have different constructors depending how you want to use them.

// Templated ListView
let items = [ 1; 2; 3; 4 ]
ListView(items) (fun item ->  // knows its `int`
    TextCell(item.ToString())
)

// Flexible ListView
ListView() [
    for i = 0 to 10 ->
        TextCell(i.ToString())
]

Note: This is all super experimental, it is completely based on a “compilable” syntax, not actually ran. A lot of hurdles are to be expected before having a working sample.

14reactions
TimLarivierecommented, May 10, 2020

It’s infinitely better in the IDEs with this new syntax. Screenshot 2020-05-10 at 11 56 12 PM Screenshot 2020-05-10 at 11 56 51 PM

Read more comments on GitHub >

github_iconTop Results From Across the Web

Experiments: Stronger typing, Mixing Dynamic/Static views, ...
A SwiftUI-like DSL. I really like the Fabulous DSL, though IDEs make it difficulty to know what's exactly is available when having like...
Read more >
F# Weekly #20, 2020 – Microsoft Build 2020 (May 19-20)
Welcome to F# Weekly, A roundup of F# content from this past week: News SAFE Stack: Functional Web Programming for .
Read more >
How Swift 5.3 enhances SwiftUI's DSL
A roundup of some of the key ways in which Swift 5.3 enhances the overall experience of building views using SwiftUI.
Read more >
Breaking up with JavaScript front ends
There is a reason why modern UI frameworks like SwiftUI or Jetpack Compose look more like React rather than pure HTML/CSS/JavaScript.
Read more >
CombineRex on CocoaPods.org
SwiftRex is a Redux implementation on top of Combine, RxSwift or ReactiveSwift. This package implements SwiftRex using RxSwift.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found