[Dart] Organize namespaces in Flutter bindings
This is about bindings for Flutter when targeting Dart, it could about bindings in general with Snake Island but each language/API has some peculiarities so let's focus on Flutter for now.
I assume Flutter will be the bindings most used for Fable>Dart users. I compare it to the Fable.Browser bindings but they will be used even more often because with React you don't need to access native Browser APIs that often.
To recap, with Fable.Browser bindings we decided:
- Put bindings for each API (DOM, WebGL, etc) in separate packages
- Put all types in all packages in the
Browser.Typesnamespace - Put values exposed by each API/Package in a module named like the package (e.g.
Browser.Dom) but with AutoOpen, so users would have access to all values of all referenced packages just withopen Browser
The decision to use Browser.Types was done because many times you don't need to access directly the types in Browser APIs. And also because we had problems previously with Fable.Core.JS namespace which contains an Option interface. Because of this, when users opened Fable.Core.JS the F# Option module got obscured and we wanted to prevent that (at the time F# could open two modules with the same name at the same time, but not a module and a class/interface, this seems to be solved now).
Bindings in Dart are quite similar to JS with a few differences.
Importattributes and helpers work the same in Dart as with JS (exceptImportDefault)- Unlike npm packages, where there's usually an entry point file, AFAIK in Dart you always need to specify the file within the package you want to import (e.g.
package:flutter/services.dart) - Unlike the Browser APIs, in Flutter you need to access the types all the time, particularly the Widgets constructors when declaring the UI
- Besides the types, Flutter modules/files ("libraries" in Dart's parlance) expose a few functions. As with JS we need to represent this a static members in a class because of the F# limitation of not having optional arguments in module functions
Rigth now, I'm having a single file for each Flutter module: https://github.com/alfonsogarciacaro/fable-flutterapp/tree/8128ae867b0c8d9761434536049fee299beeb3c9/src/Fable.Flutter
Flutter.Foundation
Flutter.Gestures
Flutter.Services
Flutter.Painting
Flutter.Semantics
...
Probably we should add AutoOpen to most of the modules as it gets tedious to open all of them, except a couple of specific ones like Material and Cupertino. So users only need to open Flutter. Note that Dart itself does something similar by re-exporting most of the exports, so you only need to import the last module (like material).
What I'm not sure about is what to do with the functions. For now I'm just adding adding a static class with the same name as the module so users call it like this:
open Flutter.Widgets
open Flutter.Material
let openDialog text =
Material.showDialog(...)
let main() =
MyApp() |> Widgets.runApp
The problem with this is when checking Flutter samples, users will have a hard time to identify where the functions come from so they don't know they have to qualify showDialog with Material and runApp with Widgets. Right now I can think of two solutions for this.
- Create a single
Flutterstatic class and add all functions to it as extensions. It will be similar to theAutoOpensolution for type modules, but we probably also need to exclude Material and Cupertino modules, and I assume it will be more difficult to automate this solution. - Just duplicate the re-exported functions in every module. So users can access
runAppboth from Material and Widgets (but notshowDialogfrom Widgets), just as it happens in Dart.
Thoughts? @Nhowka @MangelMaxime
Hello @alfonsogarciacaro, personally opening a few modules doesn't cause me any problem because it doesn't pollute the namespace.
However, it is true that having a similar experience as with native Futter/Dart can ease the usage.
As you explaining, this is something that we did well with the Browser API as in JavaScript the Browser API is global. So in F# what we did is, allow user to have access to only the APIs it needs by splitting in different packages Browser.Dom, Browser.Svg, etc. and have a single open statement open Browser.
Which allows us to have a close to JavaScript API without polluting too much the namespaces.
About showDialog and runApp, if I understand correctly having the prefix is something that is specific to Fable because of F# limitation or something.
Did you try to use static member and open types from F# ? F# documentation
For example, in one of my binding I do something like that:
// Binding code
namespace Feliz.Iconify
open Feliz
open Fable.Core
open Fable.Core.JsInterop
module Offline =
[<Erase>]
type Exports =
static member inline Icon (properties : #ISvgAttribute list) =
Interop.reactApi.createElement(import "Icon" "@iconify/react/dist/offline", createObj !!properties)
// Consumer code
open type Feliz.Iconify.Offline.Exports
Icon [ ] // Here I can directly access `Icon` because it is available now
This allow me to have a close to 1-1 match compared to JavaScript API:
import { Icon } from '@iconify/react/dist/offline';
<Icon />
I like how the open type consumer code looks. It could be frictionless if we have some guidance on what should be open in most cases. As for the other packages, if they don't cause issues when marked with [<AutoOpen>], it should be fine.
Maybe we will get some wrappers to make the framework more F# and less like Dart, so perhaps those top-level functions will be a good fit for the first one.
I was a bit worried that users had to call open type explicitly in the call site every time, but Don Syme just reminded me AutoOpen can be used with static classes now too, so that should work 👍
I was a bit worried that users had to call
open typeexplicitly in the call site every time, but Don Syme just reminded meAutoOpencan be used with static classes now too, so that should work 👍
Oh 😲 I didn't know that