Working with JavaScript in native Flutter applications.

Kyrylo Kharchenko
4 min readMar 2, 2021

Flutter is very effective and intuitive cross-platform framework that already used by 40% of the engineers who are developing multiplatform applications. It utilizes Dart as it’s programming language, so developers could create applications for Android, iOS and lately macOS, Windows and Linux. Framework covers a lot of corner cases which are present in BL and UI / UX development. For communication with native code Flutter has MethodChannel class that enables sending messages that correspond to method calls in Kotlin / Swift.

However, what if we want more? What if we need dynamic communication between Dart and JavaScript? We can build this type of connection with flutter_jscore plugin.

What is flutter_jscore?

flutter_jscore — plugin that provides the ability to evaluate JavaScript code from within our Fluttter application. flutter_jscore uses dart:ffi for interoperability with the C programming language. This helps us to have the same performance across all programming languages since that C language lib is used. Here is more info.

Dependencies

To start let’s add dependencies to pubspeck.yaml

// pub
dependencies:
flutter_jscore: ^last_version
// import
dependencies:
flutter_jscore:
path: Your local path
// git
dependencies:
flutter_jscore:
git:
url: git://github.com/xuelongqy/flutter_jscore.git

JSContext

Object of type JSContext contains execution state to run and evaluate JavaScript. JSContext have association with JSContextGroup. Contexts that created in one group have common access to all objects. If we don’t pass the group during the creation of the context the unique group will be created.

final context = JSContext.createInGroup();

Running JavaScript

Function evaluate is responsible for execution of our JavaScript code.

final resultJsValue = context.evaluate(jsScript);

The result will be a JsValue object. For string representation we should call a function with the same name.

JSValue jsValue = jsContext.evaluate(‘1 + 1’);
print(jsValue.string);

Basic functionality

Okay, we now saw a simple example of evaluation, but what if our requirements more complex than just hard-coded JavaScript? Thankfully flutter_jscore has a mechanism to dynamically communicate between Dart and JavaScript.

Example

I have created a demo application that has the ability to validate objects base on a set of characteristics. In this example I’ve tried to use all basic aspects of the framework: create objects, functions, properties, arrays, obtain a result and catching an exception.

JavaScript code

JavaScript validation code.

As we can see in the demo and code above we should send an object of type Cake, an array of objects with type Guest and ‘flutterPrint’ function for debug purposes.

Creating primitives

For creating primitives we should use static functions of JSValue class.

JSValue.makeBoolean(this.context, bool boolean) JSValue.makeNumber(this.context, double number) JSValue.makeString(this.context, String string) JSValue.makeSymbol(this.context, String description) JSValue.makeUndefined(this.context)
JSValue.makeNull(this.context)

Here is an example of creating primitives in the demo:

JSValue.makeNumber(context, cake.pieces.toDouble())
JSValue.makeString(context, cake.topping)

Creating objects

For creating objects we should use static functions of JSObjects class. Class has all functions for creating standard JavaScript objects including Date and Error. Here is an example of creating Date object. In this case, array is a list of arguments in Date constructor.

final date = DateTime.now().millisecondsSinceEpoch;
final jsDate = JSObject.makeDate(context, JSValuePointer.array([JSValue.makeNumber(context, date.toDouble())]));

For creating custom objects we should first create JSClass object with static create function. It takes object of type JSClassDefinition.

final cakeClass = JSClass.create(JSClassDefinition(
version: 0,
attributes: JSClassAttributes.kJSClassAttributeNone,
className: 'Cake',
));
final cakeObject = JSObject.make(context, cakeClass);
cakeObject.setProperty(
'pieces',
JSValue.makeNumber(context, cake.pieces.toDouble()),
JSPropertyAttributes.kJSPropertyAttributeNone,
);
cakeObject.setProperty(
'hasGluten',
JSValue.makeBoolean(context, cake.hasGluten),
JSPropertyAttributes.kJSPropertyAttributeNone,
);
cakeObject.setProperty(
'topping',
JSValue.makeString(context, cake.topping),
JSPropertyAttributes.kJSPropertyAttributeNone,
);
context.globalObject.setProperty(
'cake',
cakeObject.toValue(),
JSPropertyAttributes.kJSPropertyAttributeDontDelete,
);

Example of creating an array:

guestObject.setProperty(
'favoriteToppings',
JSObject.makeArray(
context,
JSValuePointer.array(
value.favoriteToppings
.map(
(topping) => JSValue.makeString(
context,
topping,
),
)
.toList(),
),
).toValue(),
JSPropertyAttributes.kJSPropertyAttributeNone,
);

Working with functions

flutter_jscore provides all necessary tools to dynamically communicate between your Dart code and JavaScript. Here are two examples. One is getting value from JS code, another is sending value from Dart.

void setupUtil(JSContext context) {
context.globalObject.setProperty(
'flutterPrint',
JSObject.makeFunctionWithCallback(
context,
'flutterPrint',
Pointer.fromFunction(_flutterPrint),
).toValue(),
JSPropertyAttributes.kJSPropertyAttributeNone,
);
}

static Pointer _flutterPrint(
Pointer ctx,
Pointer function,
Pointer thisObject,
int argumentCount,
Pointer<Pointer> arguments,
Pointer<Pointer> exception,
) {
final context = JSContext(ctx);
if (argumentCount > 0) {
final alertMessage = JSValue(context, arguments[0]).string;
print('Print from JS: $alertMessage');
}
return nullptr;
}
// Example of returning the value
static Pointer _stringValue(
Pointer ctx,
Pointer function,
Pointer thisObject,
int argumentCount,
Pointer<Pointer> arguments,
Pointer<Pointer> exception,
) {
final context = JSContext(ctx);
return JSValue.makeString(
context,
‘Return Value’,
).pointer;
}

In both examples flutter_jscore expects a pointer to the data which is a representation of the C pointer(since we relay on dart:ffi).

Getting a result

As was pointed earlier we get JSValue object as the result of ‘evaluate’ function. You may cast JSValue object to JSObject. In the demo we expect an array of objects with type Guest, that passed validation. Here how we parse it:

ValidationResult _getValidationMessage(JSObject jsObject) {
final List<String> results = [];
final count = jsObject.copyPropertyNames().count;
if (count == 0) {
return ValidationResult.evaluationError();
}
for (var i = 0; i < count; i++) {
final propertyObject = jsObject.getPropertyAtIndex(i).toObject();
final guestName = propertyObject.getProperty('name').string;
results.add(guestName);
}
return ValidationResult('${results.join(', ')} may eat cake', true);
}

Working with exceptions

If our JavaScript code throw an exception we can check the information in the context objects that evaluated the code.

String _getContextException(JSContext context) {
final exceptionValue = context.exception.getValue(context);
if (exceptionValue.isNull) {
return null;
} else {
return exceptionValue.string;
}
}

Static all the way

Since the project is based on dart:ffi we can not have dynamic functions during declarations of our custom properties. Since Pointer.fromFuntion expects a strict set of arguments, static references to database repositories or api services are unavoidable.

In conclusion

flutter_jscore is an excellent plugin to work with JavaScript in Flutter applications. I hope this article helps somebody to ease a deep dive into the framework. Happy codding.

Code of the demo: https://github.com/chechoora/js_core_demo

--

--