Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
871 views
in Technique[技术] by (71.8m points)

dart - Use JS library in Flutter

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.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...