adrianflutur / webviewx

A feature-rich cross-platform webview using webview_flutter for mobile and iframe for web. JS interop-ready.
MIT License
83 stars 120 forks source link

Error: NoSuchMethodError: 'pay' method not found #44

Closed hariyanuar closed 2 years ago

hariyanuar commented 2 years ago

Im trying to build a webview which have a dynamic script and call a function of that script whent the page is loaded. But I'm getting no such method error on callJsMethod even though I have created the method in jsContent.

This is the log

Error: NoSuchMethodError: 'pay'
method not found
Receiver: Instance of 'JavaScriptObject'
Arguments: []
    at Object.throw_ [as throw] (http://localhost:56359/dart_sdk.js:5061:11)
    at js.JsObject._fromJs.callMethod (http://localhost:56359/dart_sdk.js:58823:19)
    at web.WebViewXController.new.callJsMethod (http://localhost:56359/packages/webviewx/src/controller/impl/web.dart.lib.js:111:35)
    at http://localhost:56359/packages/pasar_negeri/screens/transactions/transactions_screen.dart.lib.js:3938:42
    at Generator.next (<anonymous>)
    at runBody (http://localhost:56359/dart_sdk.js:38659:34)
    at Object._async [as async] (http://localhost:56359/dart_sdk.js:38690:7)
    at http://localhost:56359/packages/pasar_negeri/screens/transactions/transactions_screen.dart.lib.js:3937:151
    at _RootZone.runUnary (http://localhost:56359/dart_sdk.js:38511:59)
    at _FutureListener.then.handleValue (http://localhost:56359/dart_sdk.js:33713:29)
    at handleValueCallback (http://localhost:56359/dart_sdk.js:34265:49)
    at Function._propagateToListeners (http://localhost:56359/dart_sdk.js:34303:17)
    at async._AsyncCallbackEntry.new.callback (http://localhost:56359/dart_sdk.js:34032:27)
    at Object._microtaskLoop (http://localhost:56359/dart_sdk.js:38778:13)
    at _startMicrotaskLoop (http://localhost:56359/dart_sdk.js:38784:13)
    at http://localhost:56359/dart_sdk.js:34519:9

This is the page source code

import 'dart:convert';
import 'dart:io' show Platform;

import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/services.dart';
import 'package:webviewx/webviewx.dart';

import '../../config/config.dart';
import '../../helpers/utils.dart';
import '../../models/checkout.dart';
import '../../models/midtrans.dart';
import '../../screens/authentication/authentication_wrapper.dart';
import '../../screens/transactions/transaction_details_screen.dart';

class SnapScreen extends StatefulWidget {
  static const routeName = '/SNAP';

  const SnapScreen({Key? key}) : super(key: key);

  @override
  _SnapScreenState createState() => _SnapScreenState();
}

class _SnapScreenState extends State<SnapScreen> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void didUpdateWidget(SnapScreen oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final Checkout paymentInfo =
        ModalRoute.of(context)?.settings.arguments as Checkout;

    final mediaQuery = MediaQuery.of(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(
          'PEMBAYARAN',
        ),
        elevation: 2,
      ),
      body: Stack(
        children: <Widget>[
          WebViewX(
            height: mediaQuery.size.height - mediaQuery.padding.vertical,
            width: mediaQuery.size.width - mediaQuery.padding.horizontal,
            navigationDelegate: (navigation) async {
              if (await canLaunch(navigation.content.source)) {
                await launch(navigation.content.source);
              }

              return NavigationDecision.prevent;
            },
            initialContent: '<html></html>',
            initialSourceType: SourceType.html,
            javascriptMode: JavascriptMode.unrestricted,
            jsContent: {
              EmbeddedJsContent(
                mobileJs: """
                function pay(transactionToken, midtransUrl, midtransClientKey) {
                  var config = document.createElement('script');
                  config.type = 'text/javascript';
                  config.src = midtransUrl;
                  config.setAttribute('data-client-key', midtransClientKey);
                  config.onload = function () {
                    snap.pay(transactionToken, {
                      // Optional
                      onSuccess: function (result) {
                        Android.postMessage('ok');
                        Print.postMessage(result);
                      },
                      // Optional
                      onPending: function (result) {
                        Android.postMessage('pending');
                        Print.postMessage(result);
                      },
                      // Optional
                      onError: function (result) {
                        Android.postMessage('error');
                        Print.postMessage(result);
                      },
                      onClose: function () {
                        Android.postMessage('close');
                        Print.postMessage('close');
                      }
                    });
                  }

                  document.head.append(config);
                }
                """,
                webJs: """
                function pay(transactionToken, midtransUrl, midtransClientKey) {
                  var config = document.createElement('script');
                  config.type = 'text/javascript';
                  config.src = midtransUrl;
                  config.setAttribute('data-client-key', midtransClientKey);
                  config.onload = function () {
                    snap.pay(transactionToken, {
                      // Optional
                      onSuccess: function (result) {
                        Android('ok');
                        Print(result);
                      },
                      // Optional
                      onPending: function (result) {
                        Android('pending');
                        Print(result);
                      },
                      // Optional
                      onError: function (result) {
                        Android('error');
                        Print(result);
                      },
                      onClose: function () {
                        Android('close');
                        Print('close');
                      }
                    });
                  }

                  document.head.append(config);
                }
                """,
              )
              /*EmbeddedJsContent(
                webJs: "function callPay() { pay('${paymentInfo.token}','${Config.midtransURL}','${Config.midtransClientKey}') }",
                mobileJs: "function callPay() { pay('${paymentInfo.token}','${Config.midtransURL}','${Config.midtransClientKey}') }",
              ),*/
            },
            dartCallBacks: {
              DartCallback(
                name: 'Print',
                callBack: (receiver) {
                  print('==========>>>>>>>>>>>>>> BEGIN');
                  print('Print Message : ' + receiver.message);
                  print(receiver.message);
                  if (receiver.message != 'undefined') {
                    _handleResponse(receiver.message, paymentInfo);
                  }
                  print('==========>>>>>>>>>>>>>> END');
                },
              ),
              DartCallback(
                name: 'Android',
                callBack: (receiver) {
                  print('==========>>>>>>>>>>>>>> BEGIN');
                  print('Android Message : ' + receiver.message);
                  if (Platform.isAndroid) {
                    if (receiver.message != 'undefined') {
                      if (receiver.message == 'close') {
                        Navigator.pop(context);
                      } else {
                        _handleResponse(receiver.message, paymentInfo);
                      }
                    }
                  }
                  print('==========>>>>>>>>>>>>>> END');
                },
              ),
            },
            onWebViewCreated: (_controller) {
              print('LOADING HTML WITH SNAP TOKEN : ' + paymentInfo.token);
              _loadHtmlFromAssets(_controller, paymentInfo.token);
              //await _loadHtmlFromAssets(_controller);
            },
          ),
        ],
      ),
    );
  }

  _loadHtmlFromAssets(
    WebViewXController webViewController, transactionToken
  ) async {
    webViewController
        .loadContent('assets/html/snap.html', SourceType.html, fromAssets: true)
        .then((_) async => await webViewController.callJsMethod("pay", [transactionToken, Config.midtransURL, Config.midtransClientKey]));
  }

  _handleResponse(message, paymentInfo) {
    var title, desc;
    Midtrans? midtrans;

    Navigator.pop(context);

    if (Platform.isAndroid) {
      switch (message) {
        case 'ok':
          midtrans = Midtrans(MIDTRANS_PAYMENT_TYPE.bank_transfer,
              MIDTRANS_STATUS_CODE.MIDTRANS_STATUS_CODE_200);
          break;
        case 'pending':
          midtrans = Midtrans(MIDTRANS_PAYMENT_TYPE.bank_transfer,
              MIDTRANS_STATUS_CODE.MIDTRANS_STATUS_CODE_201);
          break;
        case 'error':
          midtrans = Midtrans(MIDTRANS_PAYMENT_TYPE.bank_transfer,
              MIDTRANS_STATUS_CODE.MIDTRANS_STATUS_CODE_202);
          break;
      }
    } else {
      midtrans = Midtrans.fromString(message);
    }
    var result = midtrans?.getResult();
    title = result?[0];
    desc = result?[1];
    if (title.length == null && desc.length == null)
      utils.toast('Something went wrong!');
    else
      _showConfirmDialog(title, desc, paymentInfo);
  }

  void _showConfirmDialog(title, desc, paymentInfo) async {
    await showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (BuildContext context) {
        return Container(
          height: 250,
          child: Padding(
            padding: const EdgeInsets.all(10),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  title,
                  style: AppConfig.theme?.styles.title3,
                ),
                SizedBox(
                  height: 25,
                ),
                Text(
                  desc,
                  style: AppConfig.theme?.styles.body1,
                ),
                SizedBox(
                  height: 25,
                ),
                TextButton(
                  onPressed: () async {
                    Navigator.popUntil(context, (route) {
                      return route.settings.name ==
                          AuthenticationWrapper.routeName;
                    });
                    Navigator.pushNamed(
                        context, TransactionDetailsScreen.routeName,
                        arguments: paymentInfo.orderId);
                  },
                  child: Text('Lihat Detail Pembayaran'),
                )
              ],
            ),
          ),
        );
      },
    );
  }
}

This is the snap.html

<html>

<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script type="text/javascript">
  </script>
</head>

<body>
</body>

</html>
hariyanuar commented 2 years ago

So apparently when the webview.loadContent future is done, the js page actually hasnt been loaded yet. So I moved the code to call the js method to onPageFinished and seemed to fix the issue.

    webViewController
        .loadContent('assets/html/snap.html', SourceType.html, fromAssets: true)
        .then((_) async => await webViewController.callJsMethod("pay", [transactionToken, Config.midtransURL, Config.midtransClientKey]));
            onPageFinished: (_) async {
              await webViewXController.callJsMethod("pay", [
                paymentInfo.token,
                Config.midtransURL,
                Config.midtransClientKey
              ]);
            },