Using JavaScript in Flutter Web

Gonçalo Palma
Sep 20, 2020 · 6 min read
Photo by Romain Vignes on Unsplash

When building cross-platform applications that can run on web, mobile and desktop, we often don’t need to access the underlying platform — we are either showing static data or we are communicating with a backend server to post and display newdata.

However, there are cases in which we need to retrieve some information about the platform — device’s specifications, location or hardware such as camera and sensors — or even access a specific native library or API, which requires us to communicate with the platform’s native code.

With Android and iOS we use Platform Channels to send and receive messages from the native platform, and we can use PlatformViews to display native UI views on Flutter. But how we can do it in Flutter Web?

JavaScript and Dart

Before Flutter was revealed to the public, Dart was used to build web apps (as you can see in this article from 2013 — Develop modern web apps with Dart by Seth Ladd), which was possible due to two of its features:

This means that we have a direct way to communicate with JavaScript without the need of PlatformChannels

To use the js package, we must declare in a separate file the functions in JavaScript that we need to call with the help of the @JS annotation.

As an example, let’s look at the documentation and see how it is possible to use JSON.stringify in Dart:

Showing an Alert Window on Flutter

One of the reasons we might need to interact with JavaScript is to use a core feature, such as displaying an Alert or Confirm window. These interactions are used to warn a user before an action — be it navigating away from the website or doing a critical action such as deleting data.

Confirm Dialog

To use it, we must need to use the following code in JavaScript:

In out Flutter app, we may want to give it another name, such as showConfirm, and for that we can use the @JS annotation as in the documentation’s example:

Then, we can simply call this method on our Flutter app:

And sure enough a new confirm window is going to be displayed to the user:

Confirm dialog in Flutter app

The Problem of Multiplatform

As stated previously, when using Flutter we can deploy our apps to multiple platforms. However, in the previous example we are using a js library to run JavaScript code, something that both iOS and Android does not support.

If we try to run our app in an iOS device, we will get the following compilation error:

This means that when compiling for other platforms, we cannot use code code that has references to JavaScript or dart:html. One way to go around this is to use conditional importing, which you can read more in Antonello Galipò’s Article: Conditional imports across Flutter and Web.

We start by creating a stub class in which we declare all the methods we will be using, in this case the showConfirm method:

Then, we can have one file per platform where we specify the implementation of the showConfirm method. For our purpose, we are going to leave this as it is, since it enables us to compile our app to all platforms, if needed we will specify each implementation in the future.

Finally, we change our simple imports to conditional imports at the top of the file where we use the showConfirm method:

In summary, this is importing the stub_bundler.dart by default, however, if it verifies that the platform can access to dart.library.js(such as in Flutter Web), it will import the second file, where we have our code to show a JavaScript confirm window. This will enable us to compile our code to any platform with the only caveat being that when we call that function on any platform other than web, we will get an UnimplementedError.

Adding Custom JavaScript Libraries

While developing our apps, there might be the need to add a specific feature that has been already in the platform that we are developing for such as Google Maps.

Google Maps libraries are widely available for Android, iOS and Web, and instead of recreating it in Flutter, Google’s approach was to use these native libraries and display them in Flutter (you can see the implementation in the officialgoogle_maps_ flutter GitHub repository).

In the case of web, we don’t have a Pod file or gradle to add our dependencies, so we must add them to our web folder, either declaring them on the index.html file directly or by adding a new .js file in the structure.

To demonstrate this, we are going to add a simpler library — Voca , which allows us to manipulate our Strings like converting text to camel case.

We start by downloading the normal, not minified version, of file so that we can take a peek into the class names and comments. Next, we add it to our project, in a js folder inside web

In our index.html file, we can add the script right below the main.dart.js script:

Before integrating any other code, we run the app to verify if there are any errors present, and though the app runs, the Chrome Dev Tools (F12), shows a long error message that starts with:

Uncaught Error: Mismatched anonymous define()

A quick google search will show the Require.js website with the following page: Mismatched anonymous define() modules …. Upon further inspection we see that Require.js is a JavaScript library that enable us to load new modules into our web app, using specific functions such as define, which voca.js uses, meaning that we will need to add it to our app too.

So, let us dive into the Start page to integrate it in our Flutter app.

The first thing that we need to do is to rearrange the structure of the code to follow Require.js standards:

For require.js, we just need to download the latest binary from the Download section of the website and add it to the js folder. For the case of voca.js, we just moved the file from the root folder to the scripts/ folder.

However, in main.js file we will specify all the JavaScript dependencies our project requires. To make this module accessible in Dart, we add them to the window:

As with before, we must add this new dependency to our index.html file and remove the voca.js dependency. Since we are using require.js we need to not only add it but to add the dependency to main.js , which is explained in this StackOverflow answer.

Reading the documentation for voca.js, we see that we will need to access v in order to call each function. As such, we will create a @JS() class in Dart and specify each static method that we want to use:

To call this method, we will just need to call:

Finally, since we are want to make our project compilable for all platforms, we create a new method called toCamelCase in which we call V.camelCase directly. This means that both our stub file and the remaining mobile and desktop implementations will just need to create a method called toCamelCase instead of a specific class and a static method:

In our Flutter app, we will be able to call this function directly:

Conclusion

As with Flutter for mobile devices, it’s always possible to communicate with the underlying platform to access a native API or a specific library. For Flutter Web, it’s going to be easier since we don’t need to use a PlatformChannel and can instead call the code (almost) directly with the js library.

Thankfully, the Dart team already has provided us with a lot of out-of-the-box methods for the most common operations via the window variable 🥳

However, each time that we want to add an external library, we will need to rely on the js library and create the bindings.

You can find the repository for this article here:

https://www.twitter.com/FlutterComm

Flutter Community

Articles and Stories from the Flutter Community