Cloud Functions for Firebase の Remote Config バックグラウンド関数トリガー機能と FCM を組み合わせて使用すると、Remote Config の更新をリアルタイムで伝播できます。このシナリオでは、ダッシュボードまたは API から Remote Config テンプレートを公開またはロールバックした時点でトリガーされるファンクションを作成します。テンプレートを更新すると FCM メッセージを送信するファンクションがトリガーされます。これにより、クライアントは既存の構成が古くなっていて、次回サーバーから構成をフェッチする必要があることを認識できます。
Remote Config の更新をリアルタイムで伝播する方法を、以下で紹介します。
クライアント アプリ インスタンスを FCM トピックにサブスクライブする
FCM メッセージを、ユーザーベース全体などの大規模なクライアント アプリ インスタンス グループに送信する場合、最も効率的なメカニズムはトピック メッセージングです。Remote Config の更新をリアルタイムで受信する必要があるアプリ インスタンスごとに、PUSH_RC
のようなトピック名にサブスクライブする必要があります。
Swift
extension AppDelegate : MessagingDelegate { func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) { messaging.subscribe(toTopic: "PUSH_RC") { error in print("Subscribed to PUSH_RC topic") } } }
Objective-C
- (void)messaging:(FIRMessaging *)messaging didReceiveRegistrationToken:(NSString *)fcmToken { [[FIRMessaging messaging] subscribeToTopic:@"PUSH_RC" completion:^(NSError * _Nullable error) { NSLog(@"Subscribed to PUSH_RC topic"); }]; }
Android
@Override public void onNewToken(String s) { FirebaseMessaging.getInstance().subscribeToTopic("PUSH_RC"); }
テンプレートの更新時に FCM の ping を送信するファンクションを作成する
新しいバージョンの構成の公開、古いバージョンへのロールバックなどの Remote Config イベントに応答してファンクションをトリガーできます。テンプレートの更新をリアルタイムで伝播するには、テンプレート公開イベントをリッスンするファンクションを作成し、そのファンクションから FCM Admin SDK を使用してクライアント アプリ インスタンスにサイレント ping を送信します。
exports.pushConfig = functions.remoteConfig.onUpdate(versionMetadata => { // Create FCM payload to send data message to PUSH_RC topic. const payload = { topic: "PUSH_RC", data: { "CONFIG_STATE": "STALE" } }; // Use the Admin SDK to send the ping via FCM. return admin.messaging().send(payload).then(resp => { console.log(resp); return null; }); });
このファンクションは CONFIG_STATE
パラメータを設定し、PUSH_RC
トピックにサブスクライブしているすべてのクライアントに対し、そのパラメータを FCM メッセージのデータ ペイロードとして送信します。
クライアントで Remote Config の状態を設定する
上記手順で示したデータ ペイロードは、必ずアプリの共有設定内の CONFIG_STATE
を STALE
に設定します。これは、新しい更新済みのテンプレートが作成されたことにより(テンプレートの公開によってファンクションをトリガー済み)、アプリに格納されている Remote Config テンプレートが古くなったことを示します。通知ハンドラを更新して、この状態をテストします。
Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if (userInfo.index(forKey: "CONFIG_STATE") != nil) { print("Config set to stale") UserDefaults.standard.set(true, forKey:"CONFIG_STALE") } completionHandler(UIBackgroundFetchResult.newData) }
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { if (userInfo[@"CONFIG_STATE"]) { NSLog(@"Config set to stale"); [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"CONFIG_STALE"]; } completionHandler(UIBackgroundFetchResultNewData); }
Android
@Override public void onMessageReceived(RemoteMessage remoteMessage) { if (remoteMessage.getData().containsKey("CONFIG_STATE")) { SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); sharedPreferences.edit().putBoolean("CONFIG_STALE", true).apply(); } }
アプリの起動時に Remote Config の更新をフェッチする
Swift
func fetchConfig() { welcomeLabel.text = remoteConfig[loadingPhraseConfigKey].stringValue var expirationDuration = 3600 // If your app is using developer mode, expirationDuration is set to 0, so each fetch will // retrieve values from the service. if remoteConfig.configSettings.isDeveloperModeEnabled || UserDefaults.standard.bool(forKey: "CONFIG_STALE") { expirationDuration = 0 } remoteConfig.fetch(withExpirationDuration: TimeInterval(expirationDuration)) { (status, error) -> Void in if status == .success { print("Config fetched!") self.remoteConfig.activateFetched() } else { print("Config not fetched") print("Error: \(error?.localizedDescription ?? "No error available.")") } self.displayWelcome() } }
Objective-C
- (void)fetchConfig { self.welcomeLabel.text = self.remoteConfig[kLoadingPhraseConfigKey].stringValue; long expirationDuration = 3600; // If your app is using developer mode, expirationDuration is set to 0, so each fetch will // retrieve values from the Remote Config service. if (self.remoteConfig.configSettings.isDeveloperModeEnabled || [[NSUserDefaults standardUserDefaults] boolForKey:@"CONFIG_STALE"]) { expirationDuration = 0; } [self.remoteConfig fetchWithExpirationDuration:expirationDuration completionHandler:^(FIRRemoteConfigFetchStatus status, NSError *error) { if (status == FIRRemoteConfigFetchStatusSuccess) { NSLog(@"Config fetched!"); [self.remoteConfig activateFetched]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"CONFIG_STALE"]; } else { NSLog(@"Config not fetched"); NSLog(@"Error %@", error.localizedDescription); } [self displayWelcome]; }]; }
Android
private void fetchWelcomeMessage() { mWelcomeTextView.setText(mFirebaseRemoteConfig.getString("loading_phrase")); long cacheExpiration = 43200; // 12 hours in seconds. // If your app is using developer mode or cache is stale, cacheExpiration is set to 0, // so each fetch will retrieve values from the service. if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled() || mSharedPreferences.getBoolean("CONFIG_STALE", false)) { cacheExpiration = 0; } mFirebaseRemoteConfig.fetch(cacheExpiration) .addOnCompleteListener(this, new OnCompleteListener<Void>() { @Override public void onComplete(@NonNull Task<Void> task) { if (task.isSuccessful()) { Toast.makeText(MainActivity.this, "Fetch Succeeded", Toast.LENGTH_SHORT).show(); // After config data is successfully fetched, it must be activated before newly fetched // values are returned. mFirebaseRemoteConfig.activateFetched(); } else { Toast.makeText(MainActivity.this, "Fetch Failed", Toast.LENGTH_SHORT).show(); } mWelcomeTextView.setText(mFirebaseRemoteConfig.getString("welcome_message")); } }); }
最後に、CONFIG_STATE
が STALE
になった場合に強制的にネットワークから Remote Config の更新をフェッチする(ローカル ストレージを無視する)ロジックをアプリに追加します。アプリがネットワークからフェッチする頻度が高すぎる場合は、Firebase によってスロットル処理が行われる場合があります。スロットル処理をご覧ください。