Firebase In-App MessagingのUIをカスタマイズして運用する

Komerco事業部エンジニアの岸本(@_sgr_ksmt)です。
昨年Cloud Firestoreのrulesのテストを全てローカルエミュレータを使うように書き換えた話を書いてからだいぶ間が空いてしまいましたが投稿します。

今回はFirebase In-App Messagingを利用する際にカスタムUIを適応して運用している話をしたいと思います。

f:id:sgrksmt:20191025105713p:plain:w300

In-App Messaging

Firebase In-App Messaging(以下FIAMと呼びます)は、指定した条件で絞り込んだアクティブユーザーに対して、
メッセージやボタンのアクションを設定し、アプリ内で表示するためのFirebaseの一つの機能です。
表示形式としてはいわゆる「ポップアップ」「画面上部のバナー」といった形式で表示することが可能です。

ユーザーに出すための条件にアプリのターゲット、バージョン、オーディエンス、ユーザープロパティといった情報を活用することができるので、特定のユーザーにプロモーションを行うことが容易にできます。

また、メッセージを表示するUIはSDK側が標準で提供してくれるので、クライアント側はSDKをインストールするだけで実装完了になります。 また、FIAMの設定では文字の色、ボタンのアクションなど、ある程度カスタマイズすることも可能になっています。

f:id:sgrksmt:20191025105732p:plain:w600

標準で使う場合の難点

しかし、標準のまま活用するとUIに関して次のような問題点がでてきます。

  • 文字の大きさ、フォントを変更することができない
  • 文字や画像の並びを変更することができない
  • 一部の表示形式だとボタンの背景色を変更できない
  • ボタンの大きさを変更することができない


表示用UIを標準で用意してくれているのは大変うれしいのですが、どうしてもサービスのUIと比べると浮いてしまうことと、カスタマイズ可能な範囲が狭いというのが難点になってきます。
もしかしたら読者の中にはその標準UIが微妙でFIAMの使用を断念してしまった方も居るのではないでしょうか。 というわけで、次項からカスタムUIを適応していく方法を紹介します。 (※ちなみにKomercoはiOSアプリのみ配信している関係で以降の内容はiOSでのカスタムUIの適応の話になります。

カスタムUIを適応する

公式のドキュメントにあるこちらの内容を紐解きながら解説していきます。

Podfileの編集

Podfileを開いて、Firebase/InAppMessagingDisplayFirebase/InAppMessagingに変更してインストールし直します。

- pod 'Firebase/InAppMessagingDisplay'
+ pod `Firebase/InAppMessaging'


CustomMessageDisplayComponentクラスの作成

次に、InAppMessagingDisplayプロトコルに適合したCustomMessageDisplayComponentクラスを作成します。
displayMessage(_:displayDelegate:)メソッドを実装し、引数で渡ってくるmessageのデータを判別し「Card」「Modal」「Banner」「Image Only」それぞれ表示するUIを出し分けるようにします。 以下はCardタイプの場合に、サービス内で使用しているPopupクラスを活用して表示する例を示しています。

import Firebase
import Foundation

private enum IAMDisplay {
    case unknown
    case card(InAppMessagingCardDisplay)
    case modal(InAppMessagingModalDisplay)
    case banner(InAppMessagingBannerDisplay)
    case imageOnly(InAppMessagingImageOnlyDisplay)

    init(_ messageForDisplay: InAppMessagingDisplayMessage) {
        switch messageForDisplay.type {
        case .card:
            self = (messageForDisplay as? InAppMessagingCardDisplay).map { .card($0) } ?? .unknown
        case .modal:
            self = (messageForDisplay as? InAppMessagingModalDisplay).map { .modal($0) } ?? .unknown
        case .banner:
            self = (messageForDisplay as? InAppMessagingBannerDisplay).map { .banner($0) } ?? .unknown
        case .imageOnly:
            self = (messageForDisplay as? InAppMessagingImageOnlyDisplay).map { .imageOnly($0) } ?? .unknown
        @unknown default:
            self = .unknown
        }
    }
}

final class CustomMessageDisplayComponent: InAppMessagingDisplay {
    func displayMessage(_ messageForDisplay: InAppMessagingDisplayMessage, displayDelegate: InAppMessagingDisplayDelegate) {
        DispatchQueue.main.async {
            displayDelegate.impressionDetected?(for: messageForDisplay) // ★
            switch IAMDisplay(messageForDisplay) {
            case let .card(card):
                Popup.show(
                    title: card.title,
                    body: card.body,
                    image: URL(string: card.portraitImageData.imageURL),
                    primaryButton: card.primaryActionButton.buttonText,
                    secondaryButton: card.secondaryActionButton?.buttonText,
                    buttonActionHandler: { button in
                        switch button {
                            case .primary:
                                print(card.primaryActionURL)
                                // URLを開く処理
                            case .secondary:
                                print(card.secondaryActionURL)
                                // URLを開く処理
                        }
                    }   
                )
            case let .modal(modal):
                // Modalタイプの場合の表示実装
            }
        }
    }
}



messageForDisplay.type を見ることでどの表示形式か判定できるのでそれを活用し、更にそれぞれの表示形式で扱うクラスにダウンキャストして使用します。 各種表示形式でアクセスすることが出来る情報(プロパティは次のようになっています)

f:id:sgrksmt:20191025105720p:plain:w600


また、カスタムUIを表示する際はで示しているdisplayDelegate.impressionDetected?(for:)メソッドを呼び出す必要があります。 例で示しているPopupクラスはUI含めてご自身で実装してください。

messageDisplayComponentを指定する

CustomMessageDisplayComponentを実装できたら、In-App Messagingに適応します。 次のコードをFirebaseApp.configure()の呼び出し以降で設定します。可能であればこの呼出の直後に次のコードを書くと良いでしょう。

InAppMessaging.inAppMessaging().messageDisplayComponent = CustomMessageDisplayComponent()


これで、In-App Messagingの配信をアプリが受け取った際にカスタムUIで表示することが可能になります。

カスタムUIを使う際のルールを決めておく

Komercoでは、カスタムUI側でフォントの色などを指定して運用するようにしたため、 FIAMでのメッセージ配信の設定画面では色に関する設定はしない(無視する)ようにしています。

f:id:sgrksmt:20191025105735p:plain:w600

Before/After

ここまで実装ができると、同じ設定でも変更前後でこのようにUIが変わります。

Before After
f:id:sgrksmt:20191025105659p:plain:w300 f:id:sgrksmt:20191025105650p:plain:w300

デバッグがしやすくなるTips

ちょっとしたTipsですが、FIAMは指定したアナリティクスイベントを発火させないと表示されないですが、次のようにコードでメッセージ配信設定したアナリティクスイベントの名前を指定してあげると即座に表示させることができます。

InAppMessaging.inAppMessaging().triggerEvent("show_product_detail")



- 参考: In-App-Messagingのキャンペーンを手動で呼び出せるようになった

メッセージ表示やボタンタップ時のイベントをアナリティクスに別途送りたい

カスタムUIでメッセージ表示をしたり、ボタンを押した時に別途アナリティクスイベントを収集する場合は、messageForDisplay 変数からキャンペーン名を取得することができるため、これを活用するとどのキャンペーンでのイベント発火だったのか判断することができます。

f:id:sgrksmt:20191025105741p:plain

let campaignName = messageForDisplay.campaignInfo.campaignName // 設定したキャンペーン名が取得できる
Logger.postLog(.showInAppMessaging(campaignName: campaignName))


注意点

displayDelegate.impressionDetectedの呼び忘れに注意

displayDelegate.impressionDetected?(for:)メソッドを呼び忘れすと、SDK側でユーザーが見たかどうかの集計が行われないため、条件に設定しているアナリティクスイベントが発火するたび何度もユーザーに表示されてしまいます。

ポップアップの表示制御が必要なら別途実装する

もしカスタムUIで表示するポップアップのクラスをサービス内の別の場所で使用していたり、FIAMの制御外でも非同期通信を経て何かしら表示する可能性があったりする場合は自信で重複して表示されないように制御ロジックを実装しておきましょう。

func displayMessage(_ messageForDisplay: InAppMessagingDisplayMessage, displayDelegate: InAppMessagingDisplayDelegate) {
    DispatchQueue.main.async {
        if Popup.isAlreadyShown { return }
        // カスタムUI表示処理を続行
    }
}


カスタムUIを適応すると標準UIは使えなくなる

この方法でカスタムUIを適応した場合、標準UIを呼び出すことはできなくなります。 例えば、Card、Modalタイプであれば用意したUIを使い、Banner、Image Onlyタイプであれば標準のUIを呼び出す、といったことは不可能です。
もし標準のUIを実装したい場合は、「SDK側」のソースコードを参考に作成するか、そのタイプの使用を諦めるのも一つの手になります。

KomercoではBannerタイプ、Image Onlyタイプの配信は行わない事にしたので表示実装はしていません。

まとめ

カスタムUIを適応してあげることで、よりサービスに馴染んだ形でメッセージ配信を行うことができるのでよりプロモーションに活かせるようになると思います。
標準UIを敬遠して使ってなかった方、そこがネックでFirebaseを使っているにも関わらず自前でポップアップの配信機能を実装をしていた方、これを機にカスタムUIを適応してFIAMを使ってみてはいかがでしょうか。