Try   HackMD

InAppWebView

InAppWebView 的功用好比 HTML 的 iframe,
提供黑科技讓我們內嵌網頁以解決某些特別棘手的問題。
來討論一下它的用途和如何使用。

先來個在杰難逃鎮樓。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

為何使用

簡述一下使用場景:

  • Hybrid APP 為了減小體積而把部分業務邏輯遷移到 WEB 專案上實現。
  • 想使用的圖表庫 dart 不支援。媽的說的就是你,TradingView。

前置作業

簡述一下這個做法會需要準備哪些東西:

  • 本地 Server 或 CDN 供應網站資源
  • APP 端通信的邏輯
  • WEB 端通信的邏輯

組件

記得給容器的尺寸,不然有可能會報錯。
關鍵是 initialUrlRequest 必須是請求,這就是為何本地的網站 assets 會必須額外開啟一個本地的 server 之緣故。

@override
Widget build(BuildContext context) {
  return Container(
    height: 360,
    child: InAppWebView(
      initialUrlRequest: URLRequest(
        url: 'third-party-website-url',
      ),
      initialOptions: InAppWebViewGroupOptions(
        crossPlatform: InAppWebViewOptions(
          clearCache: false,
          supportZoom: false,
          transparentBackground: true,
        ),
      ),
      // 將所有手勢傳給 WebView
      gestureRecognizers: {
        Factory(() => EagerGestureRecognizer()),
      },
      onWebViewCreated: (controller) {
        // 綁定客製化事件
        _attachHandler(controller);
      },
      onLoadError: (_controller, _url, _code, message) {
        print('[InAppWebView]: failed to load webview, $message');
      },
    ),
  );
}

通信方式

建議先粗略看一遍下面的範例再回來這邊比較好搞懂脈絡。

一句話總結 InAppWebView 做的事情:
APP 和 WEB 兩端會各有一個 controller 進行跨端的發佈和訂閱。

若兩端彼此傳遞的參數為 object 建議配合 dart 和 typescript 兩邊定義好描述物件的 model,有點費工但至少下一個維護你程式碼的人會很感激你這麼做。

APP 端

基本上在 onWebViewCreated 事件後即可綁定自定義的訂閱事件。

透過 controller.addEventListner 訂閱來自 WEB 端的訊息。
callback 可以是非同步的( WEB 端的發布訊息會用 JavaScript 的 Promise 來等待 APP 端回傳的參數)。

// WEB -> [APP]

void _attachController (controller) {
  _controller = controller;
  
  _controller?.addEventListener(
    handlerName: 'restaurantIsReady',
    callback: (arguments) {
      // 建議把 model 訂清楚
      return RestaurantOptions(...);
    }
  );
  
  _controller?.addEventListener(
    handlerName: 'cook',
    callback: (arguments) async {
      // arguments 型別為陣列
      final Chef chef = arguments[0];
      final Order order = arguments[1];

      final course = await _cook(chef, order);
      return course;
    }
  );
}

透過 controller.evaluateJavaScript 發布訊息給 WEB 端。

// [APP] -> WEB

void footTheBill(int tableNumber) {
  _controller.evaluateJavaScript(
    source: 'window.checkout($tableNumber)'
  );
}

WEB 端

html 跟 typescript 引入的部分沒什麼重點我省略不講,
我們來看看 Window 裡面都被我定義了些什麼:

declare global {
  interface Window {
    flutter_inappwebview: {
      // WEB 端透過 callHandler 發布訊息給 APP 端
      // 第一個變數為想觸發的 APP 端 handlerName
      // 第二個以後的變數為要傳遞給該事件之 callback 的變數
      callHandler: (handlerName: string, ...args: any) => Promise<any>
    },
    // WEB端透過自定義 window 上的函式訂閱 APP 端發布的訊息
    // 小心不要跟 window 的預設函式撞名發生慘劇
    checkout: (tableNamber :int) => void
  }
}

透過 window.flutter_inappwebview.callHandler 發布訊息給 APP 端。

// [WEB] -> APP

window.addEventListener('flutterInAppWebViewPlatformReady', (event) => {
  window.flutter_inappwebview.callHandler('restaurantIsReady')
    .then((options: RestaurantOptions) => {
      // ...
    })
});

透過在 window 上的自定義事件訂閱來自 APP 端的訊息。

// APP -> [WEB]

function checkout (tableNamber :int) {
  // ...
}
window.checkout = checkout;

結語

以上就是 InAppWebView 不負責任的介紹,
如果你的 APP 真的很肥但還想要更肥,
或是你有一些很難很刁鑽的功能 APP 無法實現,
也許可以考慮接個 InAppWebView 讓人生輕鬆一點。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

參考

tags: Flutter