With my game, I’ve been working on creating a set of libraries to easily use Cordova plugins from Dart. I’m using the new “package:js” package. This new library replaces the old “dart:js” library. The replacement is much easier to use, but the documentation is lacking. I’ll detail all the steps necessary to get up and going to interoperate with the Cordova Facebook plugin’s Javascript.

First things first. Include the js package in your pubspec.yaml and get dependencies:

dependencies:

js: ">=0.6.0 <0.7.0"

Next, create a file for our code, I called mine “facebook.dart”. Original, I know.

Now comes the fun part, actually building the code.

All interop code needs to be annotated with the “@JS()” annotation and it must have a library, so lets start with that:

@JS()

library facebook;

With just this, Webstorm will freak out because it doesn’t know about the @JS annotation. Fix that by importing package:js. Imports have to be after the library declaration, so the next line after is fine:

@JS()

library facebook;



import 'package:js/js.dart';

Great, the warning went away. Next is declaring the actual interface stuff. The way I decided to structure this was to create a class that matches the Javascript version and make methods and properties static members of that class. This allows code that calls the interface to look like:

Facebook.login();

Here’s the initial structure:

@JS()

library facebook;



import 'package:js/js.dart';



@JS('facebookConnectPlugin')

class Facebook {



}

Notice that again we use the @JS annotation, but this time we pass in the name of the Javascript object we want to talk to; in this case it’s “facebookConnectPlugin”.

Let’s add the method names from the plugin’s documentation. We’ll worry about the arguments for them later.

@JS()

library facebook;



import 'package:js/js.dart';



@JS('facebookConnectPlugin')

class Facebook {

external static login();

external static logout();

external static getLoginStatus();

external static showDialog();

external static api();

external static logEvent();

external static logPurchase();

external static activateApp();

external static appInvite();

}

The important thing here is that the methods are marked as “external”. One thing that I find annoying is that if a class has the @JS annotation, all methods must be external. If all of them must be external, why make the programmer explicitly state it?

Let’s work on the method parameters. This gets a bit tricky with callbacks and such, but it’s not too bad once you’ve figured it out:

@JS('facebookConnectPlugin')

class Facebook {

external static login(List<String> perms, Function success, Function failure);

external static logout(Function success, Function failure);

external static getLoginStatus(Function success, Function failure);

external static showDialog(DialogOptions options, Function success, Function failure);

external static api(String requestPath, List<String> perms, Function success, Function failure);

external static logEvent(String name, Object params, num valueToSum, Function success, Function failure);

external static logPurchase(num value, String currency, Function success, Function failure);

external static activateApp(Function success, Function failure);

external static appInvite(Object options, Function success, Function failure);

}

There’s nothing crazy in here except for “DialogOptions”. Everything else is a regular Dart type parameter. So, what’s the deal with DialogOptions? Well, the Javascript is expecting an object that looks like this:

{

method: "share",

href: "http://example.com",

caption: "Such caption, very feed.",

description: "Much description",

picture: 'http://example.com/image.png',

share_feedWeb: true // iOS only

}

You would expect that this would be represented as a Dart Map, but maps are not directly translatable to Javascript objects. I don’t know why, but it’s true. To get around this limitation, we need to create an “anonymous” class:

@JS()

@anonymous

class DialogOptions {

external String get method;

external set method(String v);



external String get href;

external set href(String v);



external String get caption;

external set caption(String v);



external String get description;

external set description(String v);



external String get picture;

external set picture(String v);



external bool get share_feedWeb;

external set share_feedWeb(bool v);



external factory DialogOptions({

String method,

String href,

String caption,

String description,

String picture,

bool share_feedWeb: false

});

}

This class allows us to call our method like this:

Facebook.showDialog(new DialogOptions(method: 'share', href: 'http://example.com'), ...);

The JS package will convert the DialogOptions into a JS object for us. Knowing that, what about the “logEvent” and “appInvite” methods? They both take an object. Unfortunately, it looks like both methods allow arbitrary objects for that parameter. I have no idea how to go about doing that so I left them as objects. Maybe someone will be able to point me in the right direction.

Lastly we need to use the callbacks. All of the methods use success and failure callbacks. To use Dart functions inside a JS callback, we need to wrap it with a special method: allowInterop(). This creates the necessary JS that will call our Dart. Here’s an example with getLoginStatus:

Facebook.getLoginStatus(allowInterop((var response) {

print('Success login status: ${response}');

}), allowInterop((var response) {

print('Login failure: ${response}');

}));

Very important note: you must not set a type to any function that gets passed to Javascript. Even if you know what it’s going to be, dart2js will crash. I’ve filed an issue on GitHub, but it was closed because Dart 2.0 fixes the problem. (See note at the end for an update on Dart 2.0)

The response objects in those callbacks can be used just like Dart objects. For the success, response will look like:

{

authResponse: {

userID: "12345678912345",

accessToken: "kgkh3g42kh4g23kh4g2kh34g2kg4k2h4gkh3g4k2h4gk23h4gk2h34gk234gk2h34AndSoOn",

session_Key: true,

expiresIn: "5183738",

sig: "..."

},

status: "connected"

}

You can get the userId by doing this inside the callback:

var userid = response.authResponse.userID;

And we’re done! We’ve successfully, at least mostly, created a Dart-to-Javascript interop library. You can see the full file here. One nice thing I like about Dart’s interop is that we can then expand on the library. As an example, I made a library for Amazon Mobile Ads. Their Cordova plugin has a wonky interface but I created a separate library to use my interop library that evens out the rough spots.

Compare the interop way to load and show a banner ad:

AmazonMobileAds.loadAndShowFloatingBannerAd(

allowInterop((var res) {

print("Successful: ${res.booleanValue});

}),

allowInterop((var res) {

print("Failure: ${res});

}), [ad]

);

To the much easier to understand:

bool wasShown = await AmazonAds.loadAndShowFloatingBannerAd(_amazonBannerAd);

I wrapped the Javascript interop library and return a Future instead of using dumb callbacks. Instead it allows the calling script to use async/await.

Here’s an example of the wrapper:

static Future<bool> loadAndShowFloatingBannerAd(Ad ad) {

var completer = new Completer();

AmazonMobileAds.loadAndShowFloatingBannerAd(

allowInterop((var res) {

completer.complete(res.booleanValue);

}),

allowInterop((var res) {

completer.completeError(res);

}), [ad]);

return completer.future;

}

Check out the Amazon library on GitHub for more information.