Flutter - platformViewRegistry creating multiple views and will not display correctly

329 views Asked by At

To process payments in our Flutter web app we are using a form hosted in an IFrame element so the client's browser can connect directly with the payment gateway.

I want to vary the header display in the HtmlElementView according to a condition on the user's account.

When I load the app and display the form the first time, it displays the correct header. However, if I go back through the signup screens and try again with different account conditions, it will not re-render with the new header.

A possibly related issue is that the framework seems to be making a new HtmlElementView every time I go through the signup flow without restarting the app. I know this because I log the viewNum for the HtmlElementView, and it starts at 2 (for some reason) and increments from there. I suspect that the platform is displaying the initial view instead of the current one.

I would like to know:

  • How to prevent the multiple views, and
  • How to force the framework to refresh the HtmlElementView and display the new headerHtml. (As I mentioned, it logs correctly but will not display.)

Thank you!

import 'dart:async';
import "package:flutter/material.dart";
import 'package:fundy_designer_2/services/payment_service.dart';
import 'package:fundy_designer_2/services/user_service.dart';
import "package:fundy_designer_2/widgets/account/account_controller.dart";
import 'package:fundy_designer_2/widgets/account/subscription_preview.dart';
import "dart:ui" as ui;
import "package:universal_html/html.dart";

class CreditCardForm extends StatefulWidget {
  CreditCardForm(
    this.screenWidth, {
    this.screenHeight = 500,
    this.updating = false,
    this.callback,
    this.displayHeader = true,
    this.headerHtml,
  });

  final screenWidth;
  final screenHeight;
  final updating;
  final Function? callback;
  final bool displayHeader;
  final headerHtml;

  @override
  State<CreditCardForm> createState() =>
      _CreditCardFormState(screenWidth, displayHeader, headerHtml);
}

class _CreditCardFormState extends State<CreditCardForm> {
  _CreditCardFormState(this.screenWidth, this.displayHeader, this.headerHtml) {
    ccStyle = screenWidth < 600
        ? "assets/credit_card_form/cc_style_narrow.css"
        : "assets/credit_card_form/cc_style_wide.css";
    displayHeader = displayHeader;
    headerHtml = headerHtml;
    debugPrint(headerHtml);
    _registerIFrame();
  }

  late String formCode = getFormCode(
    ccStyle,
    displayHeader: displayHeader,
    headerHtml: headerHtml,
  );
  String? token;
  late final screenWidth;
  late StreamSubscription tokenSub;
  late String ccStyle;
  late bool displayHeader;
  late String headerHtml;

  _registerIFrame() {
    IFrameElement billingInfo = IFrameElement();

    billingInfo.style.height = "100%";
    billingInfo.style.width = "100%";
    billingInfo.srcdoc = formCode;
    billingInfo.style.border = "none";
    billingInfo.srcdoc = formCode;
    // ignore: undefined_prefixed_name
    ui.platformViewRegistry.registerViewFactory(
      "billingInfo",
      (int viewId) => billingInfo,
    );

    var tokenListener = (event) async {
      if (event.origin != window.location.origin ||
          event.data.substring(0, 4) != "***" ||
          event.data.length != **) {
        debugPrint('nice try!');
      } else {
        token = event.data;
        AccountController.instance.token = token;
        // debugPrint("token in tokenListener");
        // debugPrint(token);
        tokenSub.cancel();
        if (widget.updating) {
          String? oldLast4 = UserService.instance.last4;
          widget.callback!(true, true, oldLast4);
          await PmtService.instance.updateCard(token);
          String? newLast4 = UserService.instance.last4;
          widget.callback!(false, false, newLast4);
        }
        if (!widget.updating) {
          await AccountController.instance.getSubscriptionPreview();
          Navigator.pop(context);
          showDialog(
              context: context,
              builder: (context) {
                return Dialog(child: SubscriptionPreview());
              });
        }
      }
    };
    tokenSub = window.onMessage.listen(tokenListener);
  }

  Widget build(BuildContext context) {
    return SizedBox(
        height: widget.screenHeight,
        width: screenWidth,
        child: HtmlElementView(
          key: ValueKey("paymentFormKey"),
          viewType: billingInfo,
          onPlatformViewCreated: (int viewNum) {
            debugPrint('viewNum: $viewNum'); //This usually logs '2' the first time and increments from there
          },
        ));
  }
}

getFormCode(styleUrl, {displayHeader, headerHtml}) {
  String headerDisplay = displayHeader ? "block" : "none";
  debugPrint(headerHtml); //This logs the correct html, even though the iframe doesn't display it
  return """
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel = "stylesheet" href = "$styleUrl">
        <script src="https://scriptsource.com/latest/script.js"></script>

    <body>
     
        
        <form class="host-form" id="payment-form">
            $headerHtml

            <div class="form-field" id="card-info">
               //A bunch of form fields
            
            <div class="form-field submit-row">
              <button id="submit-button" type="submit">CONTINUE</button>
              <div class="loader" id="loading-circle"></div>
            </div>
        </form>


    </body>
    <script>
        var paymentForm = new PaymentForm();
        paymentForm.load({
            selector: "payment-form",
            publicKey: "****",
            type: "card",
            showLabels: false,
            serverHost: "https://serverhost.com",
            hideCardImage: true,
        });

    document.querySelector("#payment-form").addEventListener("submit", function(event) {
    var form = this;

    event.preventDefault();
    document.getElementById("submit-button").style.display = "none";
    document.getElementById("loading-circle").style.display = "block";
    chargify.token(
        form,
        function success(token) {
 
            console.log(token);

            window.parent.postMessage(token, window.parent.location.origin);

        },
        function error(err) {

            console.log("token ERROR - err: ", err);
        }
    );
});


    </script>
</html>

""";
}
1

There are 1 answers

0
rhoffen On BEST ANSWER

It turns out giving the view a different name allows it to display different headers. That is, send in a viewName as a parameter and use it to register the viewFactory and display the viewType:

ui.platformViewRegistry.registerViewFactory(
      viewName, //"newPaymentInfo" or "updatePaymentInfo" for example
      ...

Make sure the HtmlElementView reflects the name in the viewType property:

    return SizedBox(
        height: widget.screenHeight,
        width: screenWidth,
        child: HtmlElementView(
          key: ValueKey("paymentFormKey"),
          viewType: viewName, //again, "newPaymentInfo" or "updatePaymentInfo" for example
          onPlatformViewCreated: (int viewNum) {
            debugPrint('viewNum: $viewNum'); //This usually logs '2' the first time and increments from there
          },
        ));
  }

No other parts of the code had to change (except accepting a viewName as a parameter).