在 Flutter 中使用 JS 库

Posted

技术标签:

【中文标题】在 Flutter 中使用 JS 库【英文标题】:Use JS library in Flutter 【发布时间】:2019-02-19 03:45:28 【问题描述】:

我正在尝试在我的 Flutter 应用程序中使用 Ether JS。我知道它没有得到直接支持,甚至现有的实现也没有得到很好的记录。

有什么方法可以在我的 androidios 的 Flutter 应用程序中使用这个库?也欢迎任何其他替代建议。

我尝试过js.dart,但不知道如何使用它。我什至不确定这是否是这种情况下的正确选择。

我也试过Flutter WebView Plugin。

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

此函数正确地返回 5 作为响应。但是我不明白如何像这样使用 EtherJS 库。

我是 Flutter、Dart 和 JS 的菜鸟。任何帮助将不胜感激。

【问题讨论】:

plugin.evalJavascript( 'function add(a,b)return a+b;add(2,3);' ).then((s) print(s); 那不适用于我请添加完整代码谢谢:) @Mahmoudsalaheldiensaber 这不是问题代码。发布已经为我工作的东西的完整代码是没有意义的。 【参考方案1】:

我最终通过使用rmtmckenzie 在此answer 中建议的平台渠道解决了这个问题。

我下载了 JS 文件并将其分别保存到 Android 和 iOS 的 android/app/src/main/res/raw/ether.jsios/runner/ether.js

安装依赖项

安卓

将LiquidCore添加为应用级别build.gradle的依赖项

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

iOS

对于 iOS,我使用了 JavaScriptCore,它是 SDK 的一部分。

平台频道

在我的例子中,我需要创建一个基于我传入 JS 函数的助记符(查找 BIP39)的钱包。为此,我创建了一个平台通道,它将助记符(基本上是字符串类型)作为参数传递,完成后将返回一个 JSON 对象。

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

Android 实现 (Java)

MainActivity.java 中在这一行之后添加这个

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();
            
        
    
);

声明以下方法,这些方法执行与 JS 库交互并将结果返回到平台通道的实际操作。

@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 实现 (Swift)

override application 方法内的AppDelegate.swift 中添加以下行。

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

添加与 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

【讨论】:

截至今天,pub pub.dev/packages/flutter_liquidcore 上提供了一个液体核心插件 web 怎么样?我们如何在 Flutter web 中也使用它? @ajil - 您是否长期继续使用这种方法?生产成功了吗? 有人得到这个作品吗?可以发一下github吗?【参考方案2】:

老实说,如果您是 Flutter、Dart 和 JS 的新手,除非您愿意投入大量时间,否则您会遇到一些麻烦。它确实取决于你想用 Ether JS 库做什么,但一般来说,你很难将它与颤振集成。有一个Ethereum package,但它的范围似乎比你一直在看的 ether.js 库要窄得多——它似乎主要集中在与 RPC api 的通信而不是处理钱包等。

如果你死心塌地使用 Flutter,最好的办法是使用 Android 和 iOS 特定的库来做实际的以太坊工作,并通过 Platform Channels 与你的 dart 代码中的公共 api 进行通信.这可能是一项重大任务,具体取决于您需要公开多少 API,尤其是对于刚接触 flutter/dart 以及可能还接触 android/ios 开发的人。这将比在 webview 中运行的 javascript 来回通信性能要高得多,而且实际上可能也更容易编码,因为 Flutter 目前并没有任何好的机制来调用 js 代码。

还有另一种选择 - 完全使用不同的客户端 UI 框架。 React native 可能会做你需要的一切,并且具有使用 Javascript 的优势,因此它很可能很容易集成 Ether.js 库,尽管我不能保证它实际上会完全支持 ether.js 库(它的运行时可能例如,没有必要的加密扩展)。

【讨论】:

平台渠道看起来像我在这种情况下所需要的。我在原生 Android 和 iOS 方面有一些开发经验。这应该是我可以使用的。 啊,好吧,当您不了解某人的背景时,总是很难知道该建议什么。一旦您习惯了它的工作方式,平台通道(以及方法通道)就会相对简单。祝你好运!【参考方案3】:
    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);
      
    );
  );

【讨论】:

以上是关于在 Flutter 中使用 JS 库的主要内容,如果未能解决你的问题,请参考以下文章

在 Flutter Web 中使用 js 库

在flutter中使用Chopper网络库和flutter_bloc库

在 Flutter 组件库中使用 Intl

使用 Platform Channels 在 Flutter 中调用原生库

如何在 Flutter 应用中使用 HighCharts 飞镖库?

如何在 Flutter 中配置 Xmpp 库?