How to Use JS Library In Flutter ??

· 6 min read
How to Use JS Library In Flutter
How to Use JS Library In Flutter

If the user wants to use Ether JS in the flutter application. I know that it is not directly supported and even the existing implementations are not really well documented. So in this article, we will go through how to use the JS library in flutter.

How to Use JS Library In Flutter??

If you’re dead set on using Flutter, your best bet would be to use Android & iOS-specific libraries to do the actual ethereum stuff and to communicate through Platform Channels to a common API in your dart code.

So this could be a significant undertaking depending on how much of the API you need to expose, especially for someone new to flutter/dart and possibly to android/ios development as well.

This will be much more performant than communicating back and forth with javascript running in a web view though, and realistically probably easier to code as well because flutter doesn’t really have any good mechanisms for calling js code right now.

Users need to download the JS file and saved it to android/app/src/main/res/raw/ether.js and ios/runner/ether.js for Android and iOS respectively.

Installing dependencies

Android

Add LiquidCore as a dependency in app-level build.gradle

implementation 'com.github.LiquidPlayer:LiquidCore:0.5.0'
iOS

For iOS, we used JavaScriptCore which is part of the SDK.

Platform Channel

So if you needed to create a wallet based on a Mnemonic I pass into the JS function.

For this, If you’ve created a Platform channel that passes in the Mnemonic which is basically of type String as an argument and will return a JSON object when done.

Future<dynamic> getWalletFromMnemonic({@required String mnemonic}) {
  return platform.invokeMethod('getWalletFromMnemonic', [mnemonic]);
}

Android Implementation

Inside MainActivity.java add this after this line

GeneratedPluginRegistrant.registerWith(this);
String CHANNEL = "UNIQUE_CHANNEL_NAME";

new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
    new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
            if (methodCall.method.equals("getWalletFromMnemonic")) {
                ArrayList<Object> args = (ArrayList<Object>) methodCall.arguments;
                String mnemonic = (String) args.get(0);

                JSObject walletFromMnemonic = getWalletFromMnemonic(mnemonic);
                if (walletFromMnemonic == null) {
                    result.error("Could not create", "Wallet generation failed", null);
                    return;
                }

                String privateKey = walletFromMnemonic.property("privateKey").toString();
                String address = walletFromMnemonic.property("address").toString();

                HashMap<String, String> map = new HashMap<>();
                map.put("privateKey", privateKey);
                map.put("address", address);

                JSONObject obj = new JSONObject(map);

                result.success(obj.toString());

            } else {
                result.notImplemented();
            }
        }
    }
);

Declare the following methods which perform the actual act of interacting with the JS library and returning the result to the platform channel.

@Nullable
@VisibleForTesting
private JSObject getWalletFromMnemonic(String mnemonic) {
    JSContext jsContext = getJsContext(getEther());
    JSObject wallet = getWalletObject(jsContext);

    if (wallet == null) {
        return null;
    }

    if (!wallet.hasProperty("fromMnemonic")) {
        return null;
    }

    JSFunction walletFunction = wallet.property("fromMnemonic").toObject().toFunction();
    return walletFunction.call(null, mnemonic).toObject();
}

@Nullable
@VisibleForTesting
private JSObject getWalletObject(JSContext context) {
    JSObject jsEthers = context.property("ethers").toObject();
    if (jsEthers.hasProperty("Wallet")) {
        return jsEthers.property("Wallet").toObject();
    }
    return null;
}

@VisibleForTesting
String getEther() {
    String s = "";
    InputStream is = getResources().openRawResource(R.raw.ether);
    try {
        s = IOUtils.toString(is);
    } catch (IOException e) {
        s = null;
        e.printStackTrace();
    } finally {
        IOUtils.closeQuietly(is);
    }
    return s;
}

@VisibleForTesting
JSContext getJsContext(String code) {
    JSContext context = new JSContext();
    context.evaluateScript(code);
    return context;
}

iOS Implementation (Swift)

Add the following lines in AppDelegate.swift inside the override application method.

final let methodChannelName: String = "UNIQUE_CHANNEL_NAME"
let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
let methodChannel = FlutterMethodChannel.init(name: methodChannelName, binaryMessenger: controller)

methodChannel.setMethodCallHandler({
    (call: FlutterMethodCall, result: FlutterResult)-> Void in
    if call.method == "getWalletFromMnemonic" {
        guard let mnemonic = call.arguments as? [String] else {
            return
        }

        if let wallet = self.getWalletFromMnemonic(mnemonic: mnemonic[0]) {
            result(wallet)
        } else {
            result("Invalid")
        }
    }
})

So add the logic to interact with the JavaScriptCore.

private func getWalletFromMnemonic(mnemonic: String) -> Dictionary<String, String>? {
    let PRIVATE_KEY = "privateKey"
    let ADDRESS = "address"

    guard let jsContext = self.initialiseJS(jsFileName: "ether") else { return nil }
    guard let etherObject = jsContext.objectForKeyedSubscript("ethers") else { return nil }
    guard let walletObject = etherObject.objectForKeyedSubscript("Wallet") else { return nil }


    guard let walletFromMnemonicObject = walletObject.objectForKeyedSubscript("fromMnemonic") else {
        return nil
    }

    guard let wallet = walletFromMnemonicObject.call(withArguments: [mnemonic]) else { return nil }
    guard let privateKey = wallet.forProperty(PRIVATE_KEY)?.toString() else { return nil }
    guard let address = wallet.forProperty(ADDRESS)?.toString() else { return nil }

    var walletDictionary = Dictionary<String, String>()
    walletDictionary[ADDRESS] = address
    walletDictionary[PRIVATE_KEY] = privateKey

    return walletDictionary
}

private func initialiseJS(jsFileName: String) -> JSContext? {
    let jsContext = JSContext()
    guard let jsSourcePath = Bundle.main.path(forResource: jsFileName, ofType: "js") else {
        return nil
    }
    do {
        let jsSourceContents = try String(contentsOfFile: jsSourcePath)
        jsContext!.evaluateScript(jsSourceContents)
        return jsContext!
    } catch {
        print(error.localizedDescription)
    }
    return nil
}

So user can also create a function like the below:

    Future<String> loadJS(String name) async {
  var givenJS = rootBundle.loadString('assets/$name.js');
  return givenJS.then((String js) {
    flutterWebViewPlugin.onStateChanged.listen((viewState) async {
      if (viewState.type == WebViewState.finishLoad) {
        flutterWebViewPlugin.evalJavascript(js);
      }
    });
  });
}

Conclusion:

So in this article, we have been through how to use the JS library in flutter.

Keep Learning !!! Keep Fluttering !!!

Do let us know in the comments if you are still confused about flutter development!! we’d love to help you!!!

FlutterAgency.com is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget GuideFlutter ProjectsCode libs and etc.

FlutterAgency.com is one of the most popular online portals dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge of Flutter.

Leave a Reply