flutter_js icon indicating copy to clipboard operation
flutter_js copied to clipboard

Call a Dart function from JS and get the result back in JS context

Open talent-apps opened this issue 3 years ago • 8 comments

Is there anyway to call a Dart function from JS and get the result back in JS context? E.g.:

flutterJs.evaluate("var a = await callDartFunction(); console.log(a);");

talent-apps avatar Aug 04 '21 08:08 talent-apps

We are also trying to do this, but so far we haven't found a way. It is possible to do this in JavaScriptCore and QuickJS, but as we understand it, the only interface from JS to Dart is via sendMessage:

var upperCaseResult = sendMessage("dartToUpperCase", "hello");

Then in dart, you would have a listener:

flutterJs.onMessage('dartToUpperCase', (dynamic args) {
   var arg = (args as string);
      return arg.toUpperCase();
    });

Unfortunately upperCaseResult is undefined in javascript.

We also wondered whether this could be achieved using promise based syntax, like so:

sendMessage("dartToUpperCase", "hello").then(upperCaseResult => console.log("Result is " + upperCaseResult));

and marking the onMessage function as async but that didn't work either?

@abner Is there anyway for us to return results back to the Javascript VM from the onMessage handler?

matthewjsummers avatar Aug 16 '21 10:08 matthewjsummers

I've tried too, but in quickjs_runtime2.dart (where calls to/from QuickJS/Dart are handled) there's an error at line 238:

if (channelFunctions.containsKey(channelName)) { channelFunctions[channelName]!.call(jsonDecode(message)); } else { print('No channel $channelName registered'); }

should be instead

if (channelFunctions.containsKey(channelName)) { return channelFunctions[channelName]!.call(jsonDecode(message)); } else { print('No channel $channelName registered'); }

The missing "return" from call to channelFunctions[channelName]!.call... makes return value always null. If you change that line (i've tried in my project) you got correct return values from Dart.

@abner do you think this patch breaks something (in memory allocation or other critical places) or could be merged upstream ? Thank you for your nice work

LucaBoss74 avatar Aug 23 '21 09:08 LucaBoss74

I've made a pull-request to my fix to the problem. If anyone other than me could test it, it would be great

LucaBoss74 avatar Aug 23 '21 13:08 LucaBoss74

I've tested in our project and it doesn't seem to increase memory allocation if called repeatedly or crash the application core. I've found also that the problem is still present in Apple's JavascriptCore.

LucaBoss74 avatar Sep 14 '21 16:09 LucaBoss74

in QuickJS the Pull Request https://github.com/abner/flutter_js/pull/54 was accepted and already fixed that for QuickJS.

In AndroidCore i need to look into this more carefully. But i do think the way sendMessage was designed allows to manage the integration of Dart -> JS and JS -> Dart more decoupled, so i believe it is better. It is easy to provide multiple implementations using async or sync integrations, like we do to polyfills Xhr, console.log and others examples are already present at flutter_js codebase.

The way the engines (QuickJS and JavascriptCore) allow the Dart integration are different, so decoupled sendMessage was the best effort at the time to allow a symmetric solution.

abner avatar Sep 17 '21 11:09 abner

In my application I do this all the time and the sendMessage has never been a limitation. Until now =(.

First, let me explain how I achieved this behavior.

In my javascript code I have a utility file for managing cross-platform promises. The function registerPromise registers a promise with a unique id. From the Dart code, I can call the javascript function resolvePromise with the promise id and the resulting value to resolve the promise that is being awaited by the javascript code. Check the example below:

Javascript code:

function runDartCodeAndGetResult {
  const staticPromise = createStaticPromise()
  const promiseId = registerPromise(staticPromise)
  sendMessage('channelName', JSON.stringify({ promiseId, other: 'params' }))
  return staticPromise.promise
}

createStaticPromise() is defined here.

Dart code:

javascriptRuntime.onMessage('channelName', (dynamic args) {
  final result = doStuffAndGetResult()
  final encoded = json.encode(result)
  javascriptRuntime.evaluate('resolvePromise(${args['promiseId']}, $encoded)');
});

With this I can always call dart code and get results from Javascript. But there's a catch: the code must asynchronous!.

Until now, it has always been fine to use async code, but now, I have a specific code that should not be async. I can change it to be async, of course, but I really don't wanna do this, it would make the javascript code much more complex than it should be. For this reason I believe it would be very useful to support return values from the onMessage function (in dart). Besides making it simpler, it would also make synchronous code possible.

Tiagoperes avatar Nov 06 '21 20:11 Tiagoperes

I made a PR adding support for JavascriptCore #67. Can you please check it @abner?

Anyone using JavascriptCore can also test it please?

Tiagoperes avatar Nov 07 '21 01:11 Tiagoperes

I made a PR adding support for JavascriptCore #67. Can you please check it @abner?

Anyone using JavascriptCore can also test it please?

works on ios jscore but you cannot run async code in onMessage dart callback, for example in that callback i need to fetch something or do something that is async on the dart side, in my opinion final result = channelFunctions[channelName]!.call(jsonDecode(message)); should be changed to invoke the async callback.

iulian0512 avatar Jan 10 '22 14:01 iulian0512