LoginSignup
3
4

【Kotlin】GA4のアプリストリームにWebViewからイベントを送り検証する

Last updated at Posted at 2020-10-17

AndroidアプリのWebView内から発生させたイベントをネイティブ側のFirebase向けGoogleアナリティクス(あるいはApp+Webプロパティ、GA4)にて計測するための方法。

Googleの公式ヘルプガイドにandroid向けにはJavaのサンプルコードが記載されていたもののKoltlin版が無かったので作成しました。

1. Android プロジェクトに Firebase SDKを導入

以下リンクの「オプション1」の手順通りにやればらくらく。
https://firebase.google.com/docs/android/setup
(iOSアプリの場合はこちら : https://firebase.google.com/docs/ios/setup

最後の手順の「アプリを実行してインストールを確認」が何度リトライしても上手く行きませんが大抵スキップして翌日チェックしたら成功してます。
image.png

2. FirebaseプロジェクトをGoogleアナリティクスに接続

以下の手順でFirebaseプロジェクトをGoogleアナリティクスに接続します。

  1. 歯車アイコン > プロジェクトを設定
    image.png

  2. 「統合」タブ > Google Analytics 「有効にする」
    image.png

  3. プルダウンメニューから選択し、既存のアカウントを選択してGA4プロパティを追加するか、新規にGoogleアナリティクスアカウントを作成します。
    image.png

3. Webサイト側にJavaScript ハンドラを実装

以下Googleヘルプガイド記載のJavaScriptコードをWebViewで開かれる可能性があるすべてのWebページに実装することで、 logEvent()setUserProperty()をJavaScriptから使えるようになります。

JavaScript コード

Googleヘルプガイドのスクリプトをそのまま加工せずにご利用ください。

Android・iOS共通。アプリ側でネイティブインターフェースが実装されていない場合はconsole.log("No native APIs found.");が実行されるため、User Agentによるアプリ判定も特別な理由がない限り実装不要です。
https://firebase.google.com/docs/analytics/webview?platform=android#implement-javascript-handler
image.png

サイトへのlogEvent() 実装例

FirebaseアナリティクスのJavaScriptハンドラを実装・更にアプリ側に後述のネイティブインターフェースを実装した後、あるボタンのタップ数をFirebaseで計測したい場合、以下のようなコードでWebView内で発生したイベントをGA4に送信できるようになります。

<button type="button" onclick="logEvent('tap_button',{'param_foo':'key_bar','param_baz':'key_qux'})">logEvent Test Button</button>

image.png

取得したいイベントがGA4で推奨イベントとして用意されているものであれば可能なかぎり推奨イベント名とGoogle指定のパラメータに従います。

独自のイベント名、パラメータ名を設定しても問題ありませんが、イベント名は40文字以下の半角英数字・アンダースコアで構成され、アルファベットから始まり、パラメータの値は100文字以内である必要があります。
また、推奨イベント・推奨パラメータがadd_payment_info, item_idのように命名されているため、独自のイベントなどを定義する場合もスネークケース(小文字の英語をアンダースコアで繋ぐ記述)を使った方が整合性が保てるのでオススメです。

その他の制限事項 → Sending events  |  Measurement Protocol for Google Analytics 4

4. アプリ側にネイティブ インターフェースを実装

ヘルプガイドにKotlin版が無かったため、Javaのコードを変換して作成しています。

AnalyticsWebInterfaceクラスを実装

image.png

Kotlin コード

/** Instantiate the interface and set the context  */
class AnalyticsWebInterface (context: Context) {
    companion object{
        const val TAG = "AnalyticsWebInterface"
    }
    private val firebaseAnalytics: FirebaseAnalytics = FirebaseAnalytics.getInstance(context)
    @JavascriptInterface
    fun logEvent(name: String, jsonParams: String) {
        LOGD("logEvent:$name")
        firebaseAnalytics.logEvent(name, bundleFromJson(jsonParams))
    }

    @JavascriptInterface
    fun setUserProperty(name: String, value: String?) {
        LOGD("setUserProperty:$name")
        firebaseAnalytics.setUserProperty(name, value)
    }

    private fun LOGD(message: String) {
        // Only log on debug builds, for privacy
        if (BuildConfig.DEBUG) {
            Log.d(TAG, message)
        }
    }

    private fun bundleFromJson(json: String): Bundle? {
        if (TextUtils.isEmpty(json)) {
            return Bundle()
        }
        val result = Bundle()
        try {
            val jsonObject = JSONObject(json)
            val keys = jsonObject.keys()
            while (keys.hasNext()) {
                val key = keys.next()
                val value = jsonObject[key]
                when(value){
                    is String -> result.putString(key, value)
                    is Int -> result.putInt(key, value)
                    is Double -> result.putDouble(key, value)
                    else -> Log.w(TAG, "Value for key $key not one of [String, Integer, Double]")
                }
            }
        } catch (e: JSONException) {
            Log.w(TAG, "Failed to parse JSON, returning empty Bundle.", e)
            return Bundle()
        }
        return result
    }
}

変換元 : https://developers.google.com/analytics/devguides/collection/firebase/android/webview#implement_native_interface

必要なimport文は赤字部分にマウスオーバーして Alt+Enter で適宜追加
image.png
Javaの場合 https://firebase.google.com/docs/analytics/webview?platform=android&hl=en#implement_native_interface
(iOSアプリの場合はこちら : https://firebase.google.com/docs/analytics/webview?platform=ios#implement_native_interface

eCommerceイベントを計測したいとき

現在公式ヘルプにサンプルコードとして記載されているネイティブインターフェースのコードはitemsパラメータに対応していない。もしeCommerceイベントを計測したいときはbundleFromJson()関数を以下のように変更する。

private fun bundleFromJson(json: String): Bundle? {
    if (TextUtils.isEmpty(json)) {
        return Bundle()
    }
    val result = Bundle()
    try {
        val jsonObject = JSONObject(json)
        val keys = jsonObject.keys()
        while (keys.hasNext()) {
            val key = keys.next()
            val value = jsonObject[key]
            when(value){
                is String -> result.putString(key, value)
                is Int -> result.putInt(key, value)
                is Double -> result.putDouble(key, value)
                is JSONArray -> if(Regex("^items$").matches(key)){
                    val items = ArrayList<Bundle>()
                    for(i in 0 until value.length()){
                        val itemObj = value.getJSONObject(i)
                        val itemKeys = itemObj.keys()
                        val item = Bundle()
                        while(itemKeys.hasNext()){
                            val itemKey = itemKeys.next()
                            val itemValue = itemObj[itemKey]
                            when(itemValue) {
                                is String -> item.putString(itemKey, itemValue)
                                is Int -> item.putInt(itemKey, itemValue)
                                is Double -> item.putDouble(itemKey, itemValue)
                                else -> Log.w(TAG, "Value for item key $itemKey not one of [String, Integer, Double]")
                            }
                        }
                        items.add(item)
                    }
                    result.putParcelableArrayList(key,items)
                }else{
                    Log.w(TAG, "Value for key $key not one of [JSONArray(item)]")
                }
                else -> Log.w(TAG, "Value for key $key not one of [String, Integer, Double, JSONArray(item)]")
            }
        }
    } catch (e: JSONException) {
        Log.w(TAG, "Failed to parse JSON, returning empty Bundle.", e)
        return Bundle()
    }
    return result
}

ネイティブインターフェースをWebViewに紐づけ

先ほど作成したネイティブインターフェースをWebViewリソースに紐づけ
image.png

Kotlin コード

コード内の「 webview 」の文字を目的のリソースIDに変更の上でご利用ください。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    webview.addJavascriptInterface(AnalyticsWebInterface(this), AnalyticsWebInterface.TAG);
} else {
    Log.w(AnalyticsWebInterface.TAG, "Not adding JavaScriptInterface, API Version: " + Build.VERSION.SDK_INT);
}

(オプション)テストアプリのWebViewのデバッグを許可

もしテストアプリのWebView内での通信内容やJavaScriptの詳細な動作などの情報をPC側のChrome Deveroper Toolで確認したい場合、以下の記述も追加が必要です。
※ セキュリティ上の問題を生む場合があるため本番アプリでこの記述は行わないで下さい。

Kotlin コード

コード内の「 webview 」の文字を目的のリソースIDに変更の上でご利用ください。

webView.setWebContentsDebuggingEnabled(true);

DebugViewによるAndroidアプリのイベント計測状況の確認

手順

  1. PC にadbコマンドを導入する → ADBコマンド導入の方法 - Qiita
  2. 検証用の Android 端末にデバッグ対象のアプリをインストール
  3. Android 端末のデバッグモードを有効化
  4. Android 端末をPCにUSBデバッグモードで接続
  5. PC でコマンドプロンプトを起動し、コマンド adb devices を実行。以下のように端末名とdeviceの文字が表示されれば次の手順へ。unauthorizeと出た場合は端末側でUSBデバッグの認証が通っていないので端末を操作して許可させる。
    image.png
  1. コマンド adb shell setprop debug.firebase.analytics.app ●●●を実行(黒丸部分はデバッグ対象のパッケージ名に置換えた上で実行)
  2. Android 端末を PC から外してアプリを操作する
  3. PC のブラウザで Firebase Console を開き、目的の Firebase プロジェクト画面の左側ナビゲーション内の 分析 > DebugView を選択して開く
    image.png
  4. 「デバッグに使用するデバイス」に先ほど自分が設定した Android 端末があることを確認して選択
  5. Android 端末で操作した内容が反映されるか確認する(15秒程度の遅延あり)

イベントのデバッグ  |  Firebase

イベントが計測されている様子

以下のようにイベントが計測されれば実装および検証が成功。お疲れ様でした。

DebugViewで目的のイベントが表示されることを確認
image.png

表示されたイベントをクリックしてパラメータも正常に計測できていることを確認
image.png

GA4側のDebugViewでも確認可能
image.png


以上。

#参考
WebView でアナリティクスを使用する  |  Firebase

3
4
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
3
4