0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ネイティブアプリとwebviewでの値の受け渡し

0
Posted at

ネイティブアプリとWebView間での値の受け渡し方法についてです。

以前こちらの記事でWebViewベースでアプリを開発した際のメリットデメリットについても書きました。
よろしければこちらを参照してください。
アプリの大半をWebViewで作ってみて良かったとこ悪かったとこ

アプリの構成

  • expo
  • Next.js
  • TypeScript
    expoでネイティブアプリの部分を開発しました。アプリは会員登録してログインしてアプリのさまざまな機能を利用できます。ログイン画面はネイティブアプリになりますが、ログインした後や会員登録は全てWebViewでNext.jsで開発しています。

有名な使用例ではamazonの商品詳細ページがwebviewで作られておりアプリで見てもブラウザで見ても変わらず同じ見た目になっています。

WebViewからネイティブアプリにデータを渡す

WebViewからはwindow.ReactNativeWebView.postMessageにJSON.stringifyした文字列を渡します。
typeは何のイベントかを判定するために渡しています。例では他にidを渡していますがtokenなど他のものもいろいろ渡せます。

// WebView(Next.js)
export const customPostMessage = (message: string) => {
  if (typeof window !== 'undefined') {
    window.ReactNativeWebView?.postMessage(message);
  }
};


customPostMessage(
   JSON.stringify({
      type: 'HANDLE_ADD_FAVORITE',
      id,
  }),
);

expo側ではWebViewタグのonMessageでWebViewMessageEventを受け取りJSON.parseします。
パースした後typeでどの関数を呼ぶか判定します。messageHandlersにはWebViewから呼ばれるイベントの関数をセットしておくようにしました。

まとめるとWebViewでwindow.ReactNativeWebView.postMessageによりデータをネイティブ側に送る。
ネイティブ側ではそのデータ内にあるtypeを見て対応する関数を実行する。このような流れになっています。

  const messageHandlers: Record<string, (data: any) => void | Promise<void>> = {
    PASSWORD_RESET: async () => handleForgotPwFlg(),
    LAUNCH_QR_SCANNER: () => handleScanData(),
    COPY_TO_CLIPBOARD: (data) => handleCopyToClipboard(data.data),
    NAVIGATE_BACK: (data) => router.replace(data.destination),
    DELETE_REGISTER_AND_NAVIGATE_BACK: handleDeleteRegisterAndNavigateBack,
    NAVIGATE_TO_PASSCODE_SETUP: showPasscodeConfirmation,
    LAUNCH_SCAN_CARD_NUMBER: () => handleScanCardNumber(),
    OPEN_DIALOG: (data) => handleOpenDialog(data.dialogType),
    GET_USER_LANGUAGE: handleGetUserLanguageMessage,
    SET_USER_LANGUAGE: handleSetUserLanguageMessage,
    LAUNCH_SCAN_CARD_USER_INFO: () => handleScanUserInfo(),
    CAPTURE_SCREENSHOT: handleSaveCompleteScreen,
    SHARE_SCREENSHOT: handleShareScreen,
    PERSIST_REGISTER_STATE: handlePersistRegisterState,
    GET_REGISTER_STATE: handleResetRegisterState,
    HANDLE_SUCCESS_REGISTER: handleSuccessRegister,
    HANDLE_FAVORITE_RECIPIENT: handleFavoriteRecipient,
    GET_FAVORITE_RECIPIENTS: handleGetFavoriteRecipients,
    DELETE_TOKENS: async () => await deleteToken(),
  };
  
  const handleMessage = async (event: WebViewMessageEvent) => {
    try {
      const data = JSON.parse(event.nativeEvent.data) as WebViewMessage;
      const handler = messageHandlers[data.type];

      if (handler) {
        await handler(data);
      } else {
        console.warn(`未処理のメッセージタイプ: ${data.type}`);
      }
    } catch (error) {
      console.error('メッセージの解析に失敗しました:', error);
    }
  };
  
  return (
    <WebView
      source={{
        uri: webViewFullUrl,
      }}
      style={styles.webview}
      javaScriptEnabled={true}
      domStorageEnabled={true}
      startInLoadingState={true}
      scalesPageToFit={true}
      onMessage={handleMessage}
      injectedJavaScript={injectedJavaScript}
      ref={webViewRef}
      nestedScrollEnabled={false}
      overScrollMode="never"
      onError={handleWebViewError}
      onHttpError={handleWebViewHttpError}
      onRenderProcessGone={handleReload}
      onContentProcessDidTerminate={handleReload}
    />
  );

ネイティブからWebViewにデータを渡す

先ほどの例のWebViewタグにrefが設定されています。このrefにあるinjectJavaScriptに対してwindow.postMessageとJSON.stringifyした文字列を渡せばデータを送ることができます。

以下の例はアプリのストレージに保存してあるお気に入りユーザーをネイティブ側に伝えています。

// expo
    webViewRef.current?.injectJavaScript(`
        window.postMessage(JSON.stringify({
          type: 'GET_FAVORITES',
          favoriteRecipients:${JSON.stringify(Array.from(favoriteRecipients.entries()))}
        }), '*');
        true;
      `);

WebView側はuseEffectでmessageイベントを監視します。イベントを受け取ったらデータをパースしtypeを見て実行したいイベントかを確認します。
その後useStateにセットしています。

// webview
  useEffect(() => {
    if (isWebView()) {
      window.ReactNativeWebView?.postMessage(
        JSON.stringify({
          type: 'GET_FAVORITES',
        }),
      );

      // メッセージイベントリスナーを追加
      const handleMessage = (event: any) => {
        try {
          const data = JSON.parse(event.data);

          if (
            data.type === 'GET_FAVORITES' &&
            data.favoriteRecipients !== undefined
          ) {
            setFavoriteRecipients(new Map(data.favoriteRecipients));
          }
        } catch (error) {
          console.error('Error parsing message:', error);
        }
      };

      window.addEventListener('message', handleMessage);

      // クリーンアップ関数
      return () => {
        window.removeEventListener('message', handleMessage);
      };
    }
  }, []);

最後に

以上でネイティブアプリとWebViewで値を受け渡しできます。

イベントが多いとuseEffectが増えてしまうことやpostMessageしたイベントが何を行なっているか見えにくくなること、postMessageで渡す値の型チェックができないことなどが理由であまり多用しない方がいいと思っています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?