Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use JS library in Flutter

Tags:

I am trying to use Ether JS in my Flutter application. I know that it is not directly supported and even the existing implementations are not really well documented.

Is there any way I can use this library in my Flutter application for Android and iOS? Any other alternative suggestion is also welcome.

I have tried js.dart but could not figure out how to use it. I am not even sure if it is the right choice for this scenario.

I have also tried Flutter WebView Plugin.

plugin.evalJavascript(     'function add(a,b){return a+b;}add(2,3);' ).then((s) {     print(s); } 

This function rightly returns 5 as the response. But I do not understand how to use the EtherJS library like this.

I am a noob with Flutter, Dart and JS. Any help will be appreciated.

like image 830
Ajil O. Avatar asked Sep 14 '18 10:09

Ajil O.


People also ask

Can I use JavaScript library in Flutter?

In Flutter web, the javascript integration is possible using the package:js, mentioned in another answer. Yes.

Can Dart use Javascript libraries?

This library provides access to JavaScript objects from Dart, allowing Dart code to get and set properties, and call methods of JavaScript objects and invoke JavaScript functions. The library takes care of converting between Dart and JavaScript objects where possible, or providing proxies if conversion isn't possible.

How do I use js package in Dart?

If you pass a Dart function to a JavaScript API as an argument, wrap the Dart function using allowInterop() or allowInteropCaptureThis() . To make a Dart function callable from JavaScript by name, use a setter annotated with @JS() . @JS() library callable_function; import 'package:js/js.

Can I use node js in Flutter?

In this tutorial, I will show you how to set up both a Node. js server and a Flutter app that interacts with it. The server code will run on your local machine and the Flutter app will run in the Android emulator or iOS simulator.


1 Answers

I eventually solved this by using Platform channels as suggested by rmtmckenzie in this answer.

I downloaded 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 I used the JavaScriptCore which is part of the SDK.

Platform Channel

In my case, I needed to create a Wallet based on a Mnemonic (look up BIP39) I pass in into the JS function. For this, I created a Platform channel which 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 (Java)

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 action 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")         }     } }) 

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 } 
like image 160
Ajil O. Avatar answered Oct 10 '22 19:10

Ajil O.