Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Dart] Organize namespaces in Flutter bindings #2922

Open
alfonsogarciacaro opened this issue Jun 6, 2022 · 4 comments
Open

[Dart] Organize namespaces in Flutter bindings #2922

alfonsogarciacaro opened this issue Jun 6, 2022 · 4 comments

Comments

@alfonsogarciacaro
Copy link
Member

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.Types namespace
  • 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 with open 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.

  • Import attributes and helpers work the same in Dart as with JS (except ImportDefault)
  • 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.

  1. Create a single Flutter static class and add all functions to it as extensions. It will be similar to the AutoOpen solution 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.
  2. Just duplicate the re-exported functions in every module. So users can access runApp both from Material and Widgets (but not showDialog from Widgets), just as it happens in Dart.

Thoughts? @Nhowka @MangelMaxime

@MangelMaxime
Copy link
Member

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 />

@Nhowka
Copy link
Contributor

Nhowka commented Jun 7, 2022

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.

@alfonsogarciacaro
Copy link
Member Author

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 👍

@MangelMaxime
Copy link
Member

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 👍

Oh 😲 I didn't know that

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants