Use JS library in Flutter

后端 未结 3 1343
庸人自扰
庸人自扰 2020-12-23 12:29

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.

相关标签:
3条回答
  • 2020-12-23 13:02

    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
    }
    
    0 讨论(0)
  • 2020-12-23 13:07
        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);
          }
        });
      });
    }
    
    0 讨论(0)
  • 2020-12-23 13:23

    Honestly, if you're new to Flutter, Dart, and JS you are going to have some trouble with this unless you're willing to invest a fair amount of time. It does depend on what exactly you're trying to make with the Ether JS library, but in general you're going to have a hard time integrating it with flutter. There is an Ethereum package but it seems much narrower in scope than the ether.js library you've been looking at - it mostly seems focused on communication with the RPC api rather than dealing with wallets etc.

    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. 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 webview though, and realistically probably easier to code as well because flutter doesn't really have any good mechanisms for calling js code right now.

    There is another option - to use a different client UI framework entirely. React native might do everything you need and has the advantage of being in Javascript so it can most likely integrate the Ether.js library easily, although I can't guarantee that it will actually fully support the ether.js library (its runtime might not have the necessary crypto extensions for example).

    0 讨论(0)
提交回复
热议问题