スマホアプリ開発を加速する、Firebaseを使ってみよう

第8回新しいFirebaseの機能を実際にアプリに組み込んでみよう

前回は、Google I/0 2016で発表された新しいFirebaseの概要とそのプロダクト群をご紹介しました。

新しく追加された機能は非常に多く、とてもすべてを解説することはできませんが、今回は筆者が特に重要だと感じた以下の3つの機能を実際にAndroidアプリに組み込む方法を解説したいと思います。

  • Analytics
  • Storage
  • Remote Config

Analytics

Analyticsは新しくなったFirebaseの中核となる分析基盤です。ユーザのさまざまな行動を簡単に収集・分析することができます。

AnalyticsをAndroidで利用するにはアプリのbuild.gradleに次の依存関係を追加するだけです。

compile 'com.google.firebase:firebase-core:9.0.2'

この設定は基本的にすべてのFirebaseプロダクト群を導入する際にかならず実行しているはずなので、Analyticsは特に追加インストールする必要なく利用可能です。

イベントの記録

イベントの記録はFirebaseAnalyticsインスタンスのlogEvent()メソッドを利用します。

次の例のように、ActivityのフィールドにFirebaseAnalyticsの変数を用意しておき、onCreate()内で FirebaseAnalytics.getInstance(this)を使って初期化します。

// Activityのフィールド
private FirebaseAnalytics firebaseAnalytics;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    // インスタンスの取得
    firebaseAnalytics = FirebaseAnalytics.getInstance(this);

あとはイベントを記録したい任意の場所でlogEvent()メソッドを呼ぶだけです。

logEvent()には記録するイベントと任意のパラメータを次のようにBundle形式で渡します。

Bundle params = new Bundle();
params.putString("screen_name", "LoginActivity");
firebaseAnalytics.logEvent("open_screen", params);

これだけです。ここでは表示したページを記録するためのopen_screenというイベントを定義し、パラメータとしてscreen_nameに対応するページ情報を送信しています。

この例では記録する際にイベント名もパラメータも任意のものを指定しましたが、Firebaseがあらかじめ用意した定形のイベントとパラメータがそれぞれFirebaseAnalytics.EventFirebaseAnalytics.Paramという名前空間に定義されているのでいくつか紹介します。

イベント概要
ADD_PAYMENT_INFO支払情報を入力したイベント
ADD_TO_CART商品をカートに追加したイベント
ADD_TO_WISHLIST商品をウィッシュリストに追加したイベント
パラメータ概要
ITEM_ID商品ID
ITEM_NAME商品名
PRICE値段

これらは次のように利用します。

Bundle params = new Bundle();
params.putString(FirebaseAnalytics.Param.ITEM_ID, "12345");
params.putString(FirebaseAnalytics.Param.ITEM_NAME, "高級ブランド革の財布");
params.putDouble(FirebaseAnalytics.Param.PRICE, 29800.0);
firebaseAnalytics.logEvent(FirebaseAnalytics.Event.ADD_TO_CART, params);

ここではオンラインショッピングアプリで使うようなイベントとパラメータを一例としてご紹介しましたが、他にも数多く定義されています。詳しくはFirebaseAnalytics.EventFirebaseAnalytics.Paramのドキュメントをご参照ください。

ここに定義されたものを使わなければならないということはまったくありませんが、利用できそうなものがあれば利用してみても良いでしょう。

なお、Analyticsでは明示的にlogEvent()を呼びださなくても、導入するだけで自動的に収集されるイベントがいくつも存在します。

たとえばユーザが初めてアプリを開いたときにはfirst_openイベントが記録され、ユーザが最小セッション継続時間を超えてアプリを利用している場合にはsession_startが自動で記録されます。他にもアプリの更新やアンインストールまでも自動的に収集してくれます。詳しくは自動的に収集されるイベントのドキュメントをご参照ください。

記録したイベントは数時間程度でWebコンソールに反映されます。Analyticsのコンソールから「イベント」タブに切り替えて自分が送信したイベントが記録されていることを確認してみてください。

図1 イベント
図1 イベント

ただ、これは現行のWebコンソールの制限だと思われますが、ユーザが任意に定義したイベントの細かいプロパティはコンソール上では確認できないようです。今後の改善が望まれます。

制限事項

現状、イベントのパラメータとして独自に定義したカスタムパラメータを指定した場合、Webコンソールのイベントタブから確認した際に詳細な値の内訳を確認することができません。前述のFirebaseAnalytics.Paramで定義されたパラメータのみ細かく確認することができます。

ワークアラウンドとして既存のパラメータを流用するか、汎用的な値を表現するFirebaseAnalytics.Param.VALUEを利用する、あるいはBigQueryにエクスポートして確認する方法などが紹介されています。興味のある方はLog eventsの公式ドキュメントをご参照ください。

ユーザプロパティ

Analyticsではユーザプロパティという属性を各ユーザに設定することで、言語や地域など任意の指標でユーザをセグメント分けし、分析時のフィルタとして利用することができます。

FirebaseのWebコンソールにログインし、プロジェクトのAnalyticsから「ユーザープロパティ」を選択し「新しいユーザーのプロパティ」を選択します。

図2 ユーザープロパティ
図2 ユーザープロパティ

「ユーザープロパティ名」に任意のプロパティ名を入力し「作成」を選択します。

図3 新しいユーザープロパティの追加
図3 新しいユーザープロパティの追加

あとはプログラムからこのプロパティ名に対応する値を送信してやるだけです。

次の例ではカスタムApplicationクラスのonCreate()内でユーザープロパティを設定しています。設定にはsetUserProperty()メソッドを利用します。

FirebaseAnalytics firebaseAnalytics = FirebaseAnalytics.getInstance(this);
firebaseAnalytics.setUserProperty("design_type", "type_a");

これで以後のイベントの記録にはこのユーザープロパティが付与されて記録されるようになります。

数時間後ユーザープロパティが反映されたら、記録したイベント等をユーザープロパティでフィルタすることができます。

イベントタブから「フィルタを追加」を選択します。

図4 フィルタを追加
図4 フィルタを追加

その後「ユーザープロパティ」から指定したプロパティを選択してフィルタとして利用することができます。

図5 ユーザープロパティ
図5 ユーザープロパティ

AnalyticsのTips

Instant Runの無効

Android Studio 2.2未満でInstant Run機能を有効にしている場合、Analyticsのイベントが正しく送られないことが知られています。

もしAndroid Studio 2.2未満を使って開発している場合には、Android Studioの設定画面から次の図を参考にEnable Instant Runのチェックを外してInstant Runを無効にしてください。

図6 Instant Runを無効にする
図6 Instant Runを無効にする

デバッグ時のイベント送信確認

AnalyticsのイベントがWebコンソールに反映されるには数時間かかります。もし開発環境でイベントがきちんと記録されているか確認したい場合は、次のようにadbコマンドを実行した状態でアプリを実行してみてください。

adb shell setprop log.tag.FA VERBOSE
adb shell setprop log.tag.FA-SVC VERBOSE
adb logcat -v time -s FA FA-SVC

次のようにコンソールにイベントの記録の様子が出力されれば成功です。

(20381): onActivityCreated
(20381): Tag Manager is not found and thus will not be used
(20381): Logging event (FE): open_screen, Bundle[{screen_name=LoginActivity, _o=app}]

Storage

StorageはGoogle Cloud Storageのバケットを利用して大容量の画像やファイルを簡単かつセキュアにアップロードしたりダウンロードしたりできる機能です。

StorageをAndroidで利用するにはアプリのbuild.gradleに次の依存関係を追加します。

compile 'com.google.firebase:firebase-storage:9.0.2'
compile 'com.google.firebase:firebase-auth:9.0.2'

Storageへの操作はFirebaseStorageインスタンス越しに行います。FirebaseStorageインスタンスはFirebaseStorage.getInstance()で取得することができます。

FirebaseStorage storage = FirebaseStorage.getInstance();

リファレンス

Storageにファイルをアップロード/ダウンロードするために、Google Cloud Storageのバケットへのリファレンス(参照)を取得します。

FirebaseのWebコンソールにログイン、プロジェクトのStorageを選択し、⁠ファイル」タブ内のgs://<your-project-id>の部分をクリップボードにコピーしてください。

図7 Storageへの参照
図7 Storageへの参照

リファレンスの取得にはStorageReferenceインスタンスのgetReferenceFromUrl("gs://<your-project-id>")メソッドを利用します。gs://の部分には先ほどWebコンソールで確認したURIをペーストしてください。

StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-project-id>");

リファレンスを取得できたら、いよいよファイルを読み書きするために対象のファイルへのリファレンスを作成します。次の例は/images/sample.jpgにファイルを書き込むために、そこへの参照を作成しています。

StorageReference target = storageRef.child("images").child("sample.jpg");

.child()メソッドを連結することで、任意の深さの階層を表現することができます。これはこれまでの連載で見てきたRealtime Databaseと同じ作法なので、直感的にわかりやすいのではないでしょうか。

アップロード

Storageのリファレンスの扱いについて理解したところでいよいよファイルをアップロードしてみましょう。

アップロード方法はいくつかあるのですが、ここでは最も標準的と思われるInputStreamを利用した例を解説します。まずは次のコードをご覧ください。

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-project-id>");
StorageReference target = storageRef.child("images").child("sample.jpg");

InputStream inputStream = new FileInputStream(new File("path/to/images/target_image.jpg"));
target.putStream(inputStream)
        .addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
            @Override
            public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                Log.d(TAG, "Upload succeeded!");
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.e(TAG, "Upload failure!", e);
            }
        });

Storageへのリファレンスを取得しstorageRef.child("images").child("sample.jpg")/images/sample.jpgに対してファイルをアップロードする準備をしています。それからnew FileInputStream(new File("path/to/images/rivers.jpg"))で端末に保存されたファイルをInputStreamとしてオープンしています。あとはtarget.putStream(inputStream)にそのストリームを渡してアップロードしています。

アップロードの成功と失敗はそれぞれaddOnSuccessListener()addOnFailureListener()OnSuccessListenerOnFailureListenerリスナを渡してコールバックしてもらいます。

Webコンソールを確認して、次のようにアップロードされたファイルを確認できれば成功です。

図8 アップロードされたファイル
図8 アップロードされたファイル

InputStreamを使う以外にもandroid.net.Uriを使う方法などもあるので、詳しくはUpload Files on Androidのドキュメントをご参照ください。

ダウンロード

ダウンロードもリファレンスの取得まではまったく同じです。次の例ではサーバ上の/images/sample.jpgのリファレンスを取得し、端末ローカルの一時ファイルにダウンロードしています。

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-project-id>");
StorageReference fileRef = storageRef.child("images").child("sample.jpg");

try {
    File localFile = File.createTempFile("images", "jpg");
    fileRef.getFile(localFile)
            .addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
                @Override
                public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {
                    long totalBytes = taskSnapshot.getTotalByteCount();
                    Log.d(TAG, "Download succeeded! Total bytes: " + totalBytes);
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    Log.e(TAG, "Download failure!", e);
                }
            });
} catch (IOException e) {
    Log.e(TAG, e.getMessage(), e);
}

File.createTempFile("images", "jpg")でファイルをダウンロードする先の一時ファイルを作成し、fileRef.getFile(localFile)でファイルをダウンロードしています。

成功と失敗のコールバックはアップロード時と同様です。ログにDownload succeeded!とファイルのバイト数が表示されれば成功です。

ダウンロードもいくつか方法があるので、詳しくはDownload Files on Androidのドキュメントをご参照ください。

削除

ファイルの削除もこれまで同様ファイルへのリファレンス経由で行います。次のようにリファレンスからdelete()メソッドを呼び出すだけです。成功と失敗コールバックもこれまで同様です。

FirebaseStorage storage = FirebaseStorage.getInstance();
StorageReference storageRef = storage.getReferenceFromUrl("gs://<your-project-id>");
StorageReference fileRef = storageRef.child("images").child("sample.jpg");

fileRef.delete()
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                Log.d(TAG, "Delete succeeded!");
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.e(TAG, "Delete failure!", e);
            }
        });

Remote Config

Remote ConfigはA/Bテストを簡単に実現するための仕組みです。

たとえば「50%のユーザには別の設定値を提供する」といったシンプルな使い方のほか、⁠特定の国の特定のOSのユーザのみ商品をディスカウントする」といったような複雑なユーザセグメンテーションにも利用することができます。

今回はユーザによって画面の背景色を変える例を見てみましょう。

Webコンソールでの設定

まずはサーバ上にシンプルなキーと値のペアを定義します。Remote Configでは設定値をサーバに持ち、クライアントから適宜取得することでA/Bテストや動的な値の変更を可能にします。

WebコンソールからRemote Configを選択し「最初のパラメータを追加」を選択します。

図9 最初のパラメータを追加
図9 最初のパラメータを追加

「パラメーターキー」color⁠デフォルト値」whiteを設定します。

図10 colorを追加
図10 colorを追加

「変更を公開」を選択します。

図11 変更を公開
図11 変更を公開

確認ダイアログで「変更を公開」を選択します。

図12 変更を公開
図12 変更を公開

コンソールでの準備はいったん終了です。

Android Studioでの作業

次にAndroidのコードからRemote Configを利用します。

Remote ConfigをAndroidで利用するには、アプリのbuild.gradleに次の依存関係を追加します。

compile 'com.google.firebase:firebase-config:9.0.2'

続いてアプリのresディレクトリ以下にxmlというディレクトリを作成し、remote_config_defaults.xmlというXMLファイルを作成します。中身は次のとおりです。

<?xml version="1.0" encoding="utf-8"?>
<defaultsMap>
    <entry>
        <key>color</key>
        <value>white</value>
    </entry>
</defaultsMap>

これはクライアントサイドにおける初期値として利用します。Remote Configではサーバサイドに設定値を持ちますが、初回起動時やインターネットに接続できない場合等にこのローカルの設定を利用するのです。

したがってkeyには先ほどWebコンソールで設定したのとまったく同じcolorを設定する必要があります。valueもまずはサーバサイドと同じwhiteで問題ありません。

それではコードを書いていきます。Activityに次のようなコードを追加してください。

private View container;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // 背景
    container = findViewById(R.id.container);

    // デバッグ時には必要
    FirebaseRemoteConfigSettings remoteConfigSettings = new FirebaseRemoteConfigSettings.Builder()
            .setDeveloperModeEnabled(BuildConfig.DEBUG)
            .build();

    remoteConfig = FirebaseRemoteConfig.getInstance();
    remoteConfig.setConfigSettings(remoteConfigSettings);
    remoteConfig.setDefaults(R.xml.remote_config_defaults);

    fetch();

まずはじめに、背景色を変更するためにActivityのフィールドにViewを定義し、findViewById()で探しておきます。

次の行のFirebaseRemoteConfigSettingsはRemote Configの設定用クラスです。Remote Configは、サーバから設定値を取得する際に1時間あたり5回までしかリクエストできないという制限があります。setDeveloperModeEnabled(BuildConfig.DEBUG)することで、この設定を緩和しています。開発時は忘れないようにしましょう。

次にFirebaseRemoteConfig.getInstance()でRemote Configのインスタンスを取得し、setConfigSettings(remoteConfigSettings)で先ほどの設定を適用します。

次にremoteConfig.setDefaults(R.xml.remote_config_defaults)で最初に定義したremote_config_defaults.xmlをデフォルト値として指定します。これでもしサーバと通信できなくても初期値が利用できるので、アプリが立ち上がらないといった問題がありません。

最終行のfetch()メソッドはこのあと作成します。

サーバから情報を取得

それではサーバから情報を取得するコードを書いていきます。次のようなfetch()メソッドを作成してください。

private void fetch() {
    long cacheExpiration = 3600;
    if (remoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) {
        cacheExpiration = 0;
    }

    remoteConfig.fetch(cacheExpiration)
            .addOnSuccessListener(new OnSuccessListener<Void>() {
                @Override
                public void onSuccess(Void aVoid) {
                    Log.d(TAG, "Fetch succeeded.");
                    remoteConfig.activateFetched();

                    String colorName = remoteConfig.getString(KEY_COLOR);
                    container.setBackgroundColor(Color.parseColor(colorName));
                }
            })
            .addOnFailureListener(new OnFailureListener() {
                @Override
                public void onFailure(@NonNull Exception e) {
                    Log.e(TAG, "Fetch failed.", e);
                }
            });
}

まずremoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()で開発モードか否かを判定しています。開発モードは前述のsetDeveloperModeEnabled(BuildConfig.DEBUG)で設定したもので、それによってキャッシュ時間を変えています。Remote Configはデフォルトでサーバの設定値を12時間キャッシュするので、開発時は便宜上それを少なくする必要があるからです。

次にremoteConfig.fetch(cacheExpiration)でキャッシュ時間を指定しつつ、サーバから設定値を取得しています。成功と失敗コールバックはこれまで同様addOnSuccessListener()addOnFailureListener()で取得します。

成功時のonSuccess()内のremoteConfig.activateFetched()の部分に注目してください。ここでサーバから取得した設定を有効化しています。

設定値はremoteConfig.getString()で取得します。getString()の部分はgetBoolean(), getLogn(), getDouble()などよく使う型は用意されています。container.setBackgroundColor(Color.parseColor(colorName))で取得した値を元に背景色を設定しています。

ちなみにremoteConfig.getString()の部分はアプリを起動直後など、サーバと通信する前でもエラーなく実行することができます。最初に用意したremote_config_defaults.xmlはこのための初期値なのです。

サーバサイドで設定値を変更

アプリをビルドして立ち上げると、単に真っ白の背景色の画面が表示されると思います。これはサーバサイドの値とクライアントサイドの値がいずれもwhiteであるからです。

ではWebコンソールからcolorのデフォルト値をredなどに変更して「変更を公開」し、アプリを立ち上げ直してみてください。背景色が見事赤色に変更されれば成功です。

このようにRemote Configでは設定値をサーバサイドに持つことができるので、アプリを再リリースしなくても変更を動的に反映できるほか、たとえサーバサイドと通信できなくても問題なくアプリを動作させることができる優れた仕組みなのです。

A/Bテストしてみよう

前述のとおり、Remote ConfigではA/Bテストを簡単に行うことができます。

Webコンソールから先ほど定義したcolorパラメータを選択し「条件の値を追加」を選択します。

図13 条件の値を追加
図13 条件の値を追加

「新しい条件を定義」を選択します。

図14 新しい条件を定義
図14 新しい条件を定義

「名前」A_B_Testingとつけて「適用する条件」ユーザー(ランダム%)とし、50%と入力して「条件を作成」を選択します。

図15 新しい条件の定義
図15 新しい条件の定義

デフォルト値とそれ以外で値を変えることで50%の割合で異なった値を取得することができます。

図16 A_B_Testingの値
図16 A_B_Testingの値

なお、ここからさらに「新しい条件」を追加することもできます。たとえばユーザがAndroidならwhiteiOSならblackといった具合です。

もし複数の条件が両方マッチする場合、条件が先にマッチした順に値が決められます。たとえば「アメリカのユーザならば」という条件と「Anndroidユーザならば」という条件が両方定義されており、⁠アメリカのユーザ」という条件が先に定義されていた場合はこちらの設定値がクライアントに返されます。

Tips

サーバから設定を取得するタイミング

Remote Configでは任意のタイミングでサーバから設定を取得し、任意のタイミングでそれを有効化することができますが、現実問題としてアプリを利用中に突然A/Bテストの設定が変わって画面レイアウトが変更されるのは実用的とは言えないでしょう。

筆者のおすすめは、スプラッシュ画面で設定を取得する方法です。スプラッシュ画面表示中に設定の取得が完了すればそのまま設定を適用して表示し、万一ネットワーク不良等で設定の取得が完了しなかった場合はそのままバックエンドでリトライさせながら設定だけ取得し、反映は次回の起動に持ち越す方法です。これによりユーザにできるだけ不自然でない体験を提供することができます。

A/Bテストの効果測定

A/Bテストは出し分けるのみならず効果測定してはじめて意味があります。現状Remote Config自身には効果を計測するためのツールは付属していないようなので、Analyticsのユーザープロパティを利用するのがおすすめです。ユーザープロパティの使い方は今回のAnalyticsの節をご覧ください。

まとめ

いかがだったでしょうか。今回は新しくなったFirebaseの数多の機能の中から3つだけピックアップしてご紹介しましたが、その多機能さと可能性に圧倒されたのではないでしょうか。Firebaseにはこの他にも魅力的な新機能が数えきれないほどあります。

次回は今回に続いて、新しくなったFirebaseの機能から注目の機能をいくつか取り上げ、コード例を交えながら解説したいと思います。どうぞお楽しみに。

おすすめ記事

記事・ニュース一覧