メルカリShopsのためのWebViewの技術

こんにちは、ソウゾウのSoftware Engineerの@gentleです。連載:「メルカリShops」プレオープンまでの開発の裏側の6日目を担当いたします。
メルカリShopsは既存のメルカリアプリの中に独立したWebアプリケーションを組み込む形で開発いたしました。本記事では、Webアプリケーションとして作成されたメルカリShopsを、既存のメルカリアプリでWebViewを使って表示する際のアプリとの連携について解説いたします。

メルカリアプリとの連携

メルカリShopsはメルカリアプリ内に組み込まれておりますが、独立したWebアプリケーションとして存在しており、WebViewにてShopsのWebページを表示する形になっています。しかし、単純にアプリ内でWebページを表示しただけでは、アプリとしてのUI/UXを満たせないため、WebViewとアプリとの連携が必要です。
アプリとの連携が必要なポイントは

  • 認証
  • ログイン状態の連携
  • 複数WebViewの連携
  • 検索
  • Native Bridge
  • Deeplink

などがあります。以下それぞれ解説します。

メルカリShopsの認証

メルカリShopsは現在メルカリアプリ内で一部機能のような形でサービスを提供していますが、メルカリShopsとメルカリでは別のシステム上で稼働しています。メルカリ本体から見るとメルカリShopsはサードパーティのシステムであり、一部の機能を公開しているという位置付けになります。そのため、メルカリの認証情報とメルカリShopsの認証情報は共通ではなく、メルカリアプリ内で保持している認証情報ではメルカリShopsを参照することはできません。
しかし、メルカリShops独自のログインページを表示してメルカリアプリ内で独自にログインをさせるのはユーザー体験が非常に悪いため、メルカリアプリの認証情報を利用してメルカリShopsの認証情報を取得できるようなAPIをメルカリの認証システム側に用意していただきました。その認証情報をメルカリShopsのWebページをWebViewで開く前にWebViewのCookieに事前にセットしておくことで、メルカリShopsを認証済みの状態にできるようにしています。

メルカリのログイン状態の同期

メルカリShopsはメルカリとは独立したシステムですが、メルカリアプリ内に組み込むこともありメルカリのアカウントとメルカリShopsのアカウントは1対1で紐づいています。また、メルカリShopsは現状未ログインのユーザーには解放しておらず、メルカリアプリでログインしないと使用できません。そのため、メルカリアプリのログイン・ログアウトを検知して、メルカリShopsのWebViewの表示を切り替える必要があります。
ログイン時には、上記認証の項目で解説したメルカリShopsの認証情報を取得してCookieにセットする処理が実行されます。ログアウト時には、WebViewのCookieを削除する処理の実行と、ネイティブのログインページをメルカリShopsのWebViewの代わりに表示しています。Cookieにはメルカリのユーザーに紐づくメルカリShopsの認証情報が入っているため、メルカリのログアウト時に削除する必要があります。また、Cookieに入っている認証情報が削除されることでメルカリShopsのログインページを表示しないよう、WebViewの代わりにメルカリアプリのログインを行うネイティブのログインページを表示しています。メルカリShopsとメルカリアプリのどちらかだけがログインしている、またはそれぞれが別のアカウントでログインしている状態になってしまうことがないよう、適切に処理を行う必要があります。

複数WebViewの連携

メルカリShopsでは2種類のWebViewを使用しています。一つはメルカリアプリの起動時に表示されるショップタブの中でShopsのトップのページを表示するトップWebView、もう一つは、各商品やショップの情報を表示するページなどトップのページ以外を表示する個別WebViewになります。トップWebViewはショップタブの中にあるため戻る、進むなどのボタンがなくWebのページ遷移には対応しておりません。そのため、トップWebViewからのページ遷移は個別WebViewの画面を開き、ネイティブ側で画面遷移を行なっています。また、トップWebViewと個別WebViewでは後述のNativeBridgeの対応メソッドが異なる他、個別WebViewは検索やDeeplinkなどからトップWebViewを経由せずに直接開かれる場合もあります。
これら2種類のWebViewはSession / Cookie / LocalStrageなどをWebView間で共有する必要があります。iOSのWKWebViewでは下記のように同一のWKProcessPoolを設定することでWebView間でSessionなどを共有することができます。

static let processPool = WKProcessPool()
...中略
let configuration = WKWebViewConfiguration()
configuration.processPool = processPool
let webView = WKWebView.init(frame: .zero, configuration: configuration)

検索

メルカリアプリ内のキーワード検索でもメルカリShopsの商品を検索することができます。メルカリShopsの商品の検索にはメルカリShops独自のものではなく、メルカリの検索システムを利用しています。メルカリの検索システムの中にメルカリShopsの商品を登録することで、メルカリでの検索結果の中にメルカリShopsの商品を表示し、メルカリの商品とメルカリShopsの商品をシームレスに検索することができるようになっています。
メルカリの検索システムにメルカリShopsの商品を登録するため、メルカリShopsでの商品登録・編集時後にGCPのCloud Pub/Subを利用して、Shopsの商品情報をメルカリの検索サービスに送信しています。また、メルカリの検索システムを利用しているため、メルカリShopsの商品検索結果の画面はWebViewではなくネイティブでiOS / Android個別に実装しています。その検索結果の画面から個別の商品を表示するWebViewを開く形になっています。検索における画面遷移とネイティブ、WebViewの領域は下記の図のようになっています。
Shops画面遷移

Native Bridge

Native BridgeとはWebViewとネイティブアプリとの連携を表す言葉で、Webからネイティブアプリのメソッドを呼び出したり、ネイティブアプリから表示しているWebページのJavascriptのメソッドを呼び出すことができます。
メルカリShopsでは

  • 個別の商品等を表示するWebViewをクローズしてメルカリShopsのトップページに戻る
  • ネイティブアプリからのログの送信
  • 商品やショップのシェア機能

にNative Bridgeを利用しています。上記は全てWebからネイティブアプリのメソッドを呼び出すNative Bridgeになります。Webからネイティブアプリのメソッドを呼び出すNative Bridgeは、NativeBridgeに設定したメソッドを任意のWebサイトから呼び出される危険性があるため、特定のドメイン以外からの呼び出しには反応しないよう設定しています。
iOSのWKWebViewからネイティブアプリのメソッドを呼び出すNative BridgeはWKScriptMessageHandlerのprotocolに準拠するクラスを作成し、func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage)のメソッドを実装します。そして、下記のように前述のWKWebViewConfigurationuserContentControllerに追加します。

class NativeBridgeHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage {
        ///WebViewから呼び出される処理
    }
}
let handler = NativeBridgeHandler()
...中略
let configuration = WKWebViewConfiguration()
configuration.userContentController.add(handler, name: 'MethodName')
let webView = WKWebView.init(frame: .zero, configuration: configuration)

このようにuserContentControllerにHandlerをセットすると、WebフロントエンドからiOSのメソッドを呼び出すことができるようになります。

Firebase DynamicLinksによるDeeplinkの実装

メルカリShopsではDeeplinkにFirebase DynamicLinksを採用いたしました。iOSのDeeplinkはURL SchemeやUniversalLinksが標準で提供されており、メルカリアプリでは主にこれらを使用しています。しかし、メルカリShopsにはまだWeb版が存在していないためUniversalLinksは利用できませんでした。また、URL SchemeによるDeeplinkは対応バージョンのアプリがインストールされていない/PCからのアクセスには対応していないため、TwitterやFacebookでの商品などのシェアや、LPからの遷移には対応できませんでした。
そこでFirebase DynamicLinksを採用し、対応バージョンのアプリがインストールされていればアプリを起動して該当のページに遷移、対応バージョンのアプリがインストールされていない場合はStoreに遷移、PCからのアクセスには準備したWeb用のページをブラウザで表示するようにしました。Deeplinkから表示するWeb用のページにはDeeplinkのURLをQRコードにして表示してあり、そのQRコードをモバイル端末でスキャンすることでDeeplinkからメルカリアプリを起動して該当のページに遷移できます。このようにして、元のDeeplinkをPCからアクセスした場合でもアプリを開くよう誘導できるようになっています。

おわりに

本記事では、Webアプリケーションを既存のネイティブアプリに組み込む際の要素技術について解説いたしました。WebViewとネイティブアプリと適切に連携することで、メルカリShopsはメルカリアプリとは独立したWebアプリケーションでありつつ、1つのアプリ内でシームレスに利用できるようになっています。さらに、メルカリShopsを独立したWebアプリケーションとして開発したことで、非常に素早く開発を進め、サービスの立ち上げまで持っていくことができました。
まだまだ、これからメルカリShopsを成長させていくために開発することがたくさんありますので、ソウゾウではメルカリShopsを一緒に作っていくエンジニアを絶賛募集中です。また、ネイティブアプリの領域でもメルカリShopsのUI / UXをさらに良くしていくためどんどん開発を進めていきますので、iOS / Androidのアプリエンジニアも絶賛募集しています。もし興味があれば、下記からご連絡ください。

エンジニアとして一緒に働いてみたい方はこちら https://careers.mercari.com/jp/search-jobs/?cat=souzoh-inc

ソウゾウのメンバーとカジュアルに話してみたい方はこちら https://docs.google.com/forms/d/e/1FAIpQLSe_YPZc0uj0gCGymBGPWQv9zNEjNUMYeQwQyaQAPuvIhaQ7dQ/viewform

また、ソウゾウでは2021/08/18から2021/09/28にかけてソウゾウ TECH TALKというイベントを開催しています。テーマを分け、技術的な知見を共有し合うことを目的とした勉強会です。興味のある方はぜひご参加ください!

明日は「メルカリShopsの立ち上げを聞いたその日にプレゼン資料を投げて社内転職した話。」というタイトルでSoftware Engineerの@souが登場予定です。お楽しみに。