プログラマーのメモ書き

伊勢在住のプログラマーが気になることを気ままにメモったブログです

Firebase Remote Config を試しました

こちらの記事に書いたように、 Flutter プロジェクトで Firebase を使う準備ができたので、早速 Remote Config を試してみたので、そのメモです。

アプリ側の処理

まずは公式の説明に従って、進めてみたいと思います。

Firebase Remote Config を使ってみる

手順通り、 Remote Config を使うのに必要なライブラリを pubspec.yaml に追加します。

  • firebase_remote_config
  • firebase_analytics

ここまでは問題なし。

続いて、 RemoteConfig オブジェクトを取得するあたりからが、コードの一部しかないため、ちょっと全体がみえないです。

なので、ググって見つけた下記の記事および、それが参照している Youtube を参考にして進めてみます。

RemoteConfig を使うための実装

Firebase をセットアップした際の、プロジェクトの lib/main.dart にある _MyHomePageState クラスを修正してきます。

RemoteConfig のシングルトンは、このクラスのメンバー変数として取得します。

class _MyHomePageState extends State<MyHomePage> {
  final _remoteConfig = FirebaseRemoteConfig.instance;
  bool isLoading = false;

また、 RemoteConfig の設定処理中であることをあらわす変数 isLoading を追加しておきます。

RemoteConfig に関する設定処理は initState内で行います。

  @override
  void initState() {
    dev.log("initState");
    super.initState();
    _initRemoteConfig();
  }

  void _initRemoteConfig() async {
    dev.log("_initRemoteConfig");
    setState(() {
      isLoading = true;
    });

    dev.log("remote config: defaults");
    await _remoteConfig.setDefaults(
      {
        "first_name": "Junichi",
        "family_name": "MORI",
        "age": 30,
        "hasChild": false,
      }
    );

    dev.log("remote config: configuration settings");
    await _remoteConfig.setConfigSettings(RemoteConfigSettings(
        fetchTimeout: const Duration(minutes: 1),
        minimumFetchInterval: const Duration(seconds: 10),
    ));

    dev.log("remote config: fetch and activate");
    await _remoteConfig.fetchAndActivate();

    dev.log("call set state");
    setState(() {
      isLoading = false;
    });
  }

コードをみればわかるように、ここでは、

  • デフォルト値の設定
  • RemoteConfig の設定(タイムアウト時間、読み込みインターバル時間など)
  • 実際の値の取得と有効化の呼び出し

を行っています。なお、 dev.log というのは

import 'dart:developer' as dev;

のように定義した dart:developer パッケージのログになります。

RemoteConfig から取得した値(または未取得の場合のデフォルト値)を表示する部分はこんな感じにしておきます。

  @override
  Widget build(BuildContext context) {
    dev.log("build");
    dev.log("isLoading: $isLoading");

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: isLoading
            ? const CircularProgressIndicator()
            : Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  const Text(
                    'Remote Config contents:',
                  ),
                  Text('First Name: ${_remoteConfig.getString('first_name')}'),
                  Text('Family Name: ${_remoteConfig.getString('family_name')}'),
                  Text('Age: ${_remoteConfig.getInt('age')}'),
                  Text('Has child ?: ${_remoteConfig.getBool('hasChild')}'),
                ],
              ),
      ),
    );
  }

isLoading で Widget を分けて、 RemoteConfig の設定が完了する前であれば、処理中のくるくる回る円を表示するようにします。設定完了後は、 RemoteConfig 経由でパラメータを取得し、その内容を表示するようなコードになっています。

実行

これでアプリ側の準備ができたので、一度実行してみます。エミュレータを立ち上げて、実行すると、下記のように、 RemoteConfig のデフォルト値が表示されます。

実は、この時点ではまだ Firebase プロジェクト側で RemoteConfig パラメータを設定していないのですが、問題なく動作しています。また、表示されている値も、上記のコードで設定したデフォルト値になっています。

Firebase プロジェクトに Remote Config のパラメータを設定

次に、 Firebase Console 側で Remote Config にパラメータを追加して、アプリ側に反映されることを確認してみます。

Firebase Console にログインして、Remote Config を選択します。初めて Remote Config を利用するときだと、

のような画面が表示されます。ここで、『構成を作成』をクリックすると、パラメータの追加画面が表示されます。

パラメータの追加は難しくなく、キー、データ型、デフォルト値を指定すればいいだけです。必要な入力が終わったら、『保存』を押します。この時点では、まだ変更は反映されていないので、ご安心ください。

最初のパラメータ追加後は、上記のような画面に変わります。以後は、『パラメータの追加』ボタンを押して、同様にパラメータを追加していきます。このとき、Firebase Console 側で設定した値が反映されたのがわかりやすいように、最低一つはデフォルト値と異なる値を設定しておきます。

パラメータの設定ができたら、画面上部に表示されている

『変更を公開』ボタンを押しておきます。設定したパラメータの内容はこんな感じです。

氏名を日本語表記にしました。ここまでできたら、アプリをもう一度起動します。すると、

のように、 Firebase Console で設定した値が反映されていることがわかります(氏名が日本語表記になっていると思います)。

変更の反映について

上記の例の場合は、起動時に Remote Config の fetch と activate を行っている(fetchAndActivate を呼び出している)ため、アプリ起動時に内容が反映されていました。起動時以外に RemoteConfig の内容を更新するためには、アプリ側で fetch と activate を適宜呼び出して、 Remote Config を反映させるタイミングを制御してやる必要があります。

これがちょっと面倒なら、 Remote Config のパラメータが変更されたらそれをすぐに反映させる方法もあります。今回はそれを試しました。

変更をすぐに反映させる方法

Firebase コンソール側でのパラメータの変更をアプリ側にすぐに反映させたいこともあるかと思います。その場合は、さきほどの initState 内の _initRemoteConfig のところに、 RemoteConfig が変化したらすぐに反映するための処理を追加しておきます。

    dev.log("remote config: fetch and activate");
    await _remoteConfig.fetchAndActivate();

    _remoteConfig.onConfigUpdated.listen(
        (event) async {
          await _remoteConfig.activate();
          setState(() {});
        }
    );

    dev.log("call set state");

こんな感じですね。Firebase プロジェクト側の Remote Config のパラメータに変更があれば、 _remoteConfig.onConfigUpdated.listen が呼ばれるので、この内部で setState を呼び出して、再描画させる感じです。

準備ができたら、早速試してみます。 Firebase Console 側で Remote Config を選択し、パラメータを変更します。今回は age を 100 にしてみます。

さきほどと同様に、画面上部に表示されている『変更を公開』ボタンを押します。すると、

すぐにエミュレータ内の画面表示がかわりましたね。おぉ、結構すごいな。

RemoteConfig の設定値のダウンロードについて

ここまで試すと、まず一つ思うのが、パラメータのキーとかを間違えないようにするため、 Firebase コンソールで設定した RemoteConfig パラメータの内容をアプリ側のデフォルト値として使えないかということです。

ドキュメントを読むと、プラットフォーム毎に定められたフォーマットで現在の設定値をダウンロードできるようです。

ですが、ここをよく見ると、 Android / iOS / Web があるだけで、 Fluttter に対応するフォーマットはありません。対応するフォーマットは、それぞれ

  • Android -> xml
  • iOS -> plist
  • Web -> json

になっています。

Flutter なのに、デフォルト値の処理を Android / iOS のネイティブ側でやんないといけないのは今一つな感じです。どうしたものかな?と考えたのですが、 Flutter の場合も Web と同様に JSON 形式のファイルをダウンロードし、これを asset としてプロジェクトに含めるようにして、 Flutter のコードで asset の json ファイルを読み込み、これをデコードして、Map に変換し、 RemoteConfig#setDefaults に渡すようにします。

こうしておけば、 Fluttter 配下の JSON ファイルを更新するだけで、プラットフォームに寄らずに、デフォルト値を更新することもできるようになるかと思います。

では、早速やってみます。

Firebase コンソールから対象のプロジェクトを選択し、 RemoteConfig を選択します。『パラメータ』タブ内の設定部分にある

三点ボタンをクリックします。表示されたメニューから、

『デフォルト値をダウンロード』を選択します。すると、下記のようなダイアログが表示されるので、

希望するフォーマットを選択して、『ファイルをダウンロード』ボタンを押します。今回は json フォーマットを選択します。

内容を見てみると、

{
  "age": "30",
  "hasChild": "true",
  "first_name": "純一",
  "family_name": ""
}

こんな感じです。

(参考)警告内容について

なお、上記のファイル形式を選択する際のダイアログは、中央にでかでかと警告が載っています。

警告: アプリ内のデフォルト値を使用するように設定されたパラメータは空になります。この機能を使用する場合は、すべてのパラメータのデフォルト値を構成することをおすすめします。

この日本語だと、一瞬パラメータが空になってしまうのか、と思うかもしれませんが、そうではありません。ここでいう『アプリ内のデフォルト値を使用するように設定されたパラメータ』というのは、パラメータの編集画面を開いた際の

これが有効になっているパラメータのことを指しています。このトグルボタンを有効に変更すると、

このように変化し、パラメータの値を入力できなくなります。この状態になっているパラメータについては、パラメータが空になる、という意味の警告です(値が未設定なので当然ですよね)。

なので、値を入力済みのものが新たに空になるというわけではないので、ご安心ください。

Flutter プロジェクトで読み込み

次に、このファイルを Flutter で扱えるようにします。まずは pubspec.yaml を

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/

のようにして、 assets フォルダ以下を読み込み対象とします。

そのうえで、assets フォルダにさきほどダウンロードした json ファイルをコピーしておきます(もし、 assets フォルダがなければ作成します)。

次は、これをさきほどの設定処理で読み込んで設定するようにします。

    dev.log("remote config: defaults");
//    await _remoteConfig.setDefaults(
//        {
//          "first_name": "Junichi",
//          "family_name": "MORI",
//          "age": 30,
//          "hasChild": false,
//        }
//    );
    String default_data = await rootBundle.loadString("assets/remote_config_defaults.json");
    Map<String, dynamic> json_map = json.decode(default_data);
    await _remoteConfig.setDefaults(json_map);

json への変換は dart:convert パッケージを利用しています。なお、assets からの読み込みと json への変換は下記を参考にしました。

FlutterでローカルのJSONファイルを読み込む #Dart - Qiita

ここまでできたら、一度、 Firebase プロジェクト側のパラメータをすべて削除してから、実行してみます。この場合も、問題なくデフォルト値が表示されているのがわかります。

(参考) Firebase Console プロジェクトの Remote Config 設定の復元

なお、 Firebase プロジェクト側の Remote Config の設定も簡単に復元することができます。

たとえば、上記の削除前に、『現在の構成ファイルをダウンロード』を呼び出し、パラメータや(今回はやってませんが)各種条件の設定などをファイルとしてダウンロードしておきます。

こうすることで、『ファイルから公開』を選択すると、

のように、構成ファイルの json ファイルを指定するように求められます。ここで、さきほどダウンロードした json ファイルを反映することで、パラメータを再度設定することができます。

なかなか便利ですね。

なお、Remote Config のパラメータ等はバージョニングされて管理されており、これら一つのまとまったものを Remote Config テンプレートと呼んでいるようです。

まとめ

実装の見通しさえ一度わかれば、使うのはそんなに難しいもんではなさそうですね。あと、Remote Config で扱えるデータ型として JSON があるので、これを使えば結構大きな初期データの設定にも使うことができそうです。これは、使っていきたいですね。