Fable icon indicating copy to clipboard operation
Fable copied to clipboard

[Help!] Dart/Flutter bindings

Open alfonsogarciacaro opened this issue 2 years ago β€’ 28 comments

I think we discussed it before but it'll be nice to have a semi-automatic way to generate Dart/Flutter bindings so users can start writing apps when the compiler is ready, so I'm opening this issue for discussion.

I've uploaded a dead-simple Elmish Flutter app here: https://github.com/alfonsogarciacaro/fable-flutterapp

I manually wrote a few bindings for the things I needed in this file. In general, bindings are very similar to the JS ones, there's some discussion about writing bindings in general for Snake Island in #2779. A couple of specifics for Dart:

  • NamedParams attribute: used to indicate the method has named params in Dart. If the named params start from the 2nd or 3rd argument you can pass fromIndex (zero based) as in [<NamedParams(fromIndex=1)>]
  • IsConst indicates the method (or constructor in the case of classes) should be called with const if all arguments are constants

Ideally the bindings should be extracted from this repo. In previous conversations @Nhowka commented there are introspection tools for Dart so maybe we could use those, or is it simpler to just write a simple custom parser? Pinging also @adelarsq who also has experience with Dart and @davedawkins because he's good at writing scripts for code generation as he did with the Feliz API πŸ‘Ό

alfonsogarciacaro avatar May 11 '22 12:05 alfonsogarciacaro

Let me take a look then

davedawkins avatar May 11 '22 14:05 davedawkins

The Mirror reflection API looks promising.

davedawkins avatar May 12 '22 09:05 davedawkins

Wow. F# app on macbook image

davedawkins avatar May 12 '22 09:05 davedawkins

A very quick summary of my findings so far:

  • Flutter is based upon dart. Flutter knows what dart is, dart doesn't know what flutter is

  • Flutter can create a MacOS app, so we can write output bindings direct to file system using reflection. However:

  • Dart has Mirrors for reflection, but Flutter does not support Mirrors => we can't reflect on MaterialApp, Scaffold etc from a flutter app. An attempt to use Mirrors in Flutter produces a compilation/runtime error (I can't recall), and research on Stackoverflow backs this up

  • Dart cannot (easily) load the flutter packages (eg 'package:flutter/material.dart', or even '../../flutter/lib/material.dart' eg) => this blocks the approach of using a Dart application to reflect on the flutter packages. Searches on "dart applications that use Flutter" result in "use flutter", understandably.

  • Flutter has a relection package named "reflectable" which looks promising, but a simple example didn't compile.

What to look at next:

  • Challenge any of the "cannot" and "does not" findings above - I've tried and failed but it doesn't mean someone else can't make it work.
  • Find an example of "reflectable" that compiles and works to understand it better
  • Reference parser for Dart: https://github.com/dart-lang/sdk/wiki/The-Dart-specification-parser. We may be able to use this to parse Material.dart etc and generate the bindings
  • Hand-made fuzzy parser. Once I understand the mapping that @alfonsogarciacaro made between material.dart and Flutter.Material.fs I'll have a feel for how well this approach might work

davedawkins avatar May 12 '22 10:05 davedawkins

Dartdoc is the tool that generates this kind of documentation and the ones on pub.dev that already describes the public API for each library. Looking at the html it generates it could be feasible to parse them directly, but I still believe that the ideal solution could be reviving this issue so we could use the output to generate our bindings or forking/rewriting to our needs to generate them directly.

It even has an --auto-include-dependencies flag that generates documentation files for direct and indirect dependencies, so pretty powerful tool.

Nhowka avatar May 12 '22 12:05 Nhowka

Nice! I will help on this πŸ€—

adelarsq avatar May 12 '22 15:05 adelarsq

When importing the dartdoc package, we can create a Dartdoc instance calling the constructor Dartdoc.withEmptyGenerator. There is a brittle generator field on it with a @visibleForTesting annotation that we could leverage. The Generator receives a PackageGraph that describes everything on the package and a FileWriter to write the result.

I'm doing some experiments with it to learn the layout to extract everything we need, but that way we probably can use the tool as is.

Nhowka avatar May 14 '22 18:05 Nhowka

A hand-made parser would make sense. So would be possible to extend ts2fable to support Dart and more languages?

adelarsq avatar May 15 '22 02:05 adelarsq

I upload the test file I made in this repository. I started handling only the constructors for now and got this result for the flutter library. Function types, the generics on the arguments and the translation from core types are still not correctly handled, but it's already a start.

While it's cool that it's using the metadata from the language and loading everything needed into the context so we could handle as we want, it makes it terribly slow. I ran it against my elmish-like lib with almost no dependencies and it takes around 2 minutes to reach the generator.

Nhowka avatar May 15 '22 05:05 Nhowka

Amazing. The speed issue makes it hard to test/debug, but once completed this thing only needs to be run occasionally (to keep bindings in sync), I would think?

davedawkins avatar May 15 '22 13:05 davedawkins

Yes, we could plug some pre-analysis step to check for changes only start the generation if there is a new or different version package. Maybe we could create a project and save a copy of the pubspec.lock and compare them. If there is a change, we regenerate all files again as it is easier than detecting changes on a single package.

For the named constructors I mapped them as static methods that return an instance of the class. Is that the right way to map them?

Nhowka avatar May 15 '22 19:05 Nhowka

Thanks everybody for your help! This is great @Nhowka, even if it requires some manual tweaking it's already a great help to make some demos/samples for the alpha release πŸŽ‰ As @davedawkins says speed probably it's not an issue if we only need to run the tool once in a while. For prototyping, is it faster if we work with a small code sample or most of the boot up time is taken by Dartdoc.

@adelarsq About ts2fable, probably the only part we could reuse would be printing and it has its own AST which may not have all the information we need (const constructors, named params) so it may be faster for now to do things in Dart directly as @Nhowka did πŸ‘

alfonsogarciacaro avatar May 17 '22 11:05 alfonsogarciacaro

About compiling named constructors as static members, this is fine as in Dart they're syntactically the same in the call site. You can also use the [<IsConst>] attribute in a static member if needed: https://github.com/alfonsogarciacaro/fable-flutterapp/blob/1a80389301c32f8a763bdac93f60c864ee07608d/src/Flutter.Material.fs#L65

alfonsogarciacaro avatar May 17 '22 11:05 alfonsogarciacaro

@Nhowka I'm trying to edit your code a bit to get bindings for the constructors of the Material widgets. See this commit: https://github.com/alfonsogarciacaro/DartToFableBindings/commit/b7728ac5afbf9fd829585cd2765c9f0b14afb1e0

I tried with a test Dart package and it seemed to work fine, however I downloaded the flutter repo, followed the instructions here and managed to run flutter analyze in packages/flutter dir without issues. However when I try to run the fsgen from that dir I don't get any output. This is the command I'm using:

dart run ../../../DartToFableBindings/bin/fsgen.dart \
  --exclude 'dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart,dart:ffi,dart:html,dart:js,dart:ui,dart:js_util' \
  --show-progress  \
  --no-auto-include-dependencies  \
  --no-validate-links  \
  --no-verbose-warnings  \
  --no-allow-non-local-warnings \
  --no-allow-tools

I only get many errors, this is the shortened log (only start and finish):

../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/dartdoc-5.0.1/lib/src/model/model_element.dart:706:14: Warning: Operand of null-aware operation '?.' has type 'LineInfo' which excludes null.
 - 'LineInfo' is from 'package:analyzer/source/line_info.dart' ('../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/analyzer-3.4.1/lib/source/line_info.dart').
      return lineInfo?.getLocation(nameOffset);
             ^
Documenting flutter...
  error: private API of package:Dart is reexported by libraries in other packages:
    from cupertino.Color: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart:94:7)
    referred to by cupertino: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/cupertino.dart:23:9)
    referred to by material: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/material.dart:21:9)
    referred to by painting: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/painting.dart:18:9)
    referred to by rendering: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/rendering.dart:22:9)
    referred to by widgets: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/widgets.dart:13:9)

...

  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:424:16)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:428:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.view: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:459:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.sublistView: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:481:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
-
dartdoc 5.0.1 (/C:/Users/alfon/repos/DartToFableBindings/bin/fsgen.dart) failed: Command executables must exist. The file "c:\users\alfon\repos\flutter\bin\cache\dart-sdk\bin\dart" does not exist for tool snippet..

Did you manage to run the fsgen from the flutter repo? Can you help? πŸ™

alfonsogarciacaro avatar May 30 '22 06:05 alfonsogarciacaro

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

Nhowka avatar May 30 '22 06:05 Nhowka

You're right, that worked! Now I've a ton of bindings... I only need to make them compile πŸ˜… Getting excited now!

alfonsogarciacaro avatar May 31 '22 13:05 alfonsogarciacaro

I'm gonna take a stab at getting that code generator to produce translations for function types. I'm using @alfonsogarciacaro's latest changes to the generator and it seems like functions are still not being translated. Correct me if I'm wrong on that, and I'm open to suggestions if anyone already has ideas.

bentok avatar Jul 25 '22 21:07 bentok

Thanks a lot for your help @bentok! I think my latest changes are in this branch: https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets

I was making several attempts and also did some manual changes. I think this code can output the module functions, but in any case the generator is a bit dirty at the moment, so some cleanup would be much appreciated. See the comment from @Nhowka about the dummy project necessary to make the doc generator work πŸ‘ https://github.com/alfonsogarciacaro/DartToFableBindings/blob/f001c9621d018a082a37cd593745d7549d33f72c/lib/fsgen.dart#L358-L361

alfonsogarciacaro avatar Jul 26 '22 03:07 alfonsogarciacaro

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

@Nhowka do you think generating bindings for 3rd party Flutter libraries would require the same approach? I have made some modifications to both your code and @alfonsogarciacaro's fork (https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets) and am getting close, but can't quite get it right.

For example, if I generate bindings for flutter_secure_storage, it will generate dozens of files - some of which have only a few bindings and others that have more. However it doesn't seem like any of the files have all of the bindings.

bentok avatar Aug 08 '22 18:08 bentok

@bentok, I believe that for typical packages with standard pubspec files, the same surface outputted on their dartdoc documentation on pub.dev should be available to us. Could there be some filter pruning some of those before they reach the code generation stage?

If I'm not mistaken, we could generate bindings for them without using a dummy project, but it could be amazing if we could create bindings for all packages an actual project is referencing on the fly. We could have a cache, so we skip generating for known versions, but as dart has the advantage that the libraries are packaged with raw code that dartdoc could read, I believe it is an achievable goal.

Nhowka avatar Aug 08 '22 20:08 Nhowka

Hey, l've come late to this thread, but @bentok and I have connected to start working on this again. I'm able to reproduce everything up to @alfonsogarciacaro getting errors on his first try to run this. But I have not yet worked out what to do once the empty flutter project is set up to get the bindings. I know it's been more than a year, but if someone remember and can give the exact step that would speed up the work a bit.

johnstorey avatar Jul 29 '23 13:07 johnstorey

Hi everyone, I got the source code from https://github.com/alfonsogarciacaro/fable-flutterapp and after updating fable it compiled and ran correctly, but the auto/hot-reload does not work. Is there something that can be done to make this work?

I'm using vscode under Windows 10. Thank you.

octaviordz avatar Nov 25 '23 03:11 octaviordz

Hello,

in the readme of the repo there is a mention that you have to trigger hot reload manually.

Did you give it a try ?

I never used Flutter so I don’t know if there is some requirements like in JavaScript for it to work.

MangelMaxime avatar Nov 25 '23 10:11 MangelMaxime

Hi, I had not tried the "Ctr+F5" that works, I was just manually updating the .dart generated file to trigger the hot reload. But my question was more in regard to what is the missing/link/configuration the 'build.cmd' is running fable in watch mode so when you update the source code it creates the .dart file(s) and vscode does load the updated .dart file(s) and like I said I was manually updating the .dart file to manual trigger hot reload hence my question is why if vscode 'knows' the file has changed the hot reload is not triggered.

Thank you.

octaviordz avatar Nov 26 '23 03:11 octaviordz

Hello @octaviordz,

I am not sure I understood everything because it was a really long sentence πŸ˜…

But, the first thing I was check is what is the behaviour of hot reload in a pure Dart project because if the behaviour is the same then there is nothing that can be improve.

If not, then someone with Dart/Flutter knowledge will need to have a look at it.

MangelMaxime avatar Nov 26 '23 17:11 MangelMaxime

Hi @MangelMaxime, Hot reload works in a pure Dart project as expected. Thank you for your response.

octaviordz avatar Nov 27 '23 00:11 octaviordz